Developer forum

Forum » Development » Unit test for a scheduled task

Unit test for a scheduled task

Karol Barkowski
Reply

Hi,

so I have a scheduled task defined and it's using few of the Dynamicweb.Ecommerce.Services classes, like Shops or Products.
Now, since whenever I make a change to code, I need to restart my app, it's just very time consuming as it takes ages for DW app to start. What I'd prefer is to have a unit test to run any logic that I have included in my scheduled task.
But then, since I'm not running my code in context of a web app, I cannot use any of those Dynamicweb.Ecommerce.Services classes, they're just not instantiated.

Is there any workaround for that?


Replies

 
Morten Bengtson Dynamicweb Employee
Morten Bengtson
Reply

Hi Karol,

In general your unit tests should only be testing your own code.
Otherwise, it's an integration test and then you should be testing on a complete environment with DW, files, database, etc.

In order to perform unit tests on your own scheduled task add-in you should try to isolate all the external dependencies (DW, files, database or any other external services and libraries).
There are several ways to do that, but an easy solution is to use a derived "proxy" test class for your scheduled task add-in (see below).
Alternatively you can use third party libraries like Moq or NSubstitute for mocking in your unit tests.
Note: You might run into some issues even if you don't call any services directly anywhere and thats because some properties/methods on our domain entities (Shop, Product, etc.) will call the services when needed.
There might be someone out there who can come up with a better solution, but this approach should work for most scenarios...

// This is an example implementation of a scheduled task add-in which is going to be used in production.
[AddInName("MyScheduledTaskAddIn"), AddInLabel("My scheduled task add-in"), AddInIgnore(false)]
public class MyScheduledTaskAddIn : BaseScheduledTaskAddIn
{

    public sealed override bool Run()
    {
        List<Shop> shops = GetShops().ToList();

        foreach (var shop in shops)
        {
            ProcessShop(shop);
            SaveShop(shop);
        }

        return true;
    }

    // You can separate your own logic into smaller testable methods and call them in your unit tests, if you need to.
    internal static void ProcessShop(Shop shop)
    {
        shop.Name = $"{shop.Id} {Guid.NewGuid()}";
    }

    // You can mock this method in your unit tests by creating a derived class and overriding this method
    protected virtual void SaveShop(Shop shop)
    {
        Dynamicweb.Ecommerce.Services.Shops.Save(shop);
    }

    // You can mock this method in your unit tests by creating a derived class and overriding this method
    protected virtual IEnumerable<Shop> GetShops()
    {
        return Dynamicweb.Ecommerce.Services.Shops.GetShops();
    }
}

// This is an implementation which inherits from the actual add-in and can be used for testing.
public class TestMyScheduledTaskAddIn : MyScheduledTaskAddIn
{
    // This is a property which you can set in your unit tests to pass in some test data
    public IEnumerable<Shop> Shops { get; set; }

    // You can pass in some test data in the constructor - or just set the property above
    public TestMyScheduledTaskAddIn(IEnumerable<Shop> shops)
    {
        Shops = shops ?? Enumerable.Empty<Shop>();
    }

    protected override void SaveShop(Shop shop)
    {
        // Do something here if you want
        // Otherwise you could verify that Shops have been updated as expected in your unit tests.
    }

    protected override IEnumerable<Shop> GetShops()
    {
        return Shops;
    }
}


/ Morten

 
Karol Barkowski
Reply

Hi,

Thanks. I know that those are not unit tests. And that's not what I am trying to achieve here. I only wanted to figure out how to run my task more easily.
So, I don't want to mock anything. I actually want it to run exactly as it does when I trigger it from the admin pages. But I wanted to avoid those loooong minutes of waiting whenever I make a change in the code that I then need to try out. 
The current process looks more like this - Make a change, start the website and go grab a coffee. Few minutes pass and when the website is finally up and running I can trigger the task and see if it works. Most of the time it doesn't. So the cycle repeats. And at least 95% of that cycle is waiting for the damn website to start. It's incredibely slow.
I just wanted to find a way to avoid that and maybe trigger the logic that the task is performing from the unit test. So I could run it from VS directly. And since this is an instant action, it would reduce the amount of time I spend just verifying if my changes are correct by that 95%. 
 

 
Imar Spaanjaars Dynamicweb Employee
Imar Spaanjaars
Reply

Why not attach the Visual Studio debugger to the IIS instance that has your task? Then you can debug and make changes to your code while the task is running.

 
Morten Bengtson Dynamicweb Employee
Morten Bengtson
Reply

If you want it to run exactly as in Dynamicweb then it is an integration test.
You cannot create a unit test for a scheduled task add-in which works for all functionality in Dynamicweb without doing any sort of mocking.
Integration tests are often slow and run for several minutes which is why they are usually scheduled to run during a nigtly build on a build server.
Dynamicweb 9 is slow on startup. Unfortunately there is no quick fix for that.
We have improved the performance considerably on Dynamicweb 10 (because large parts have been completely rewritten).

Maybe someone else can suggest some alternative solutions.

 
Karol Barkowski
Reply

Yes, as I stated - I am fully aware that this is an integration test and again - I am NOT trying to write a unit test. I only want to use unit test as a way to trigger the scheduled task. I am fully aware of the differences between the unit and integration tests. That's not the point here.

Does DW10 use dependency injection mechanisms in place of those static services? That would be enough to solve the problem. I could just inject the same set of services that the website is using into basically anytihng I want - test, console app, or just anytihng that just doesn't take 10 minutes to start. And in general - dependency injection is pretty much a standart. Static services are considered as antipattern since I can remeber (so at least 17 years).

 

You must be logged in to post in the forum