Background
I was refactoring a piece of code with a colleague when we encountered a multi threaded code. We were refactoring some unit tests. As part of the test we needed to cover this code which was using BackgroundWorker to execute a long running task and updating the user interface once the background thread had completed its job. In this post I am going to demonstrate how to unit test the multi threaded code.
Scenario for using Background Worker
Assume that we have a requirement to fetch some data using a service and display it in the user interface. The user interface calls a method on a object which delegates the call to the service. While the service is processing, we do want the user interface to be responsive and give a visible feedback to the user. We are making use of the MVP pattern to separate the concerns in our project. So the view is abstracted and we program to an interface. So we are really not worried about how the view display the information to the user. We can just set a property on the view to display something like an hourglass symbol.
We call a method on the background thread using Background worker. In MVP the presenter takes care of handling such logic. So the model would abstract the part which relates to fetching the data from the data source. Lets assume that we have a repository which does this job for us. Based on these things lets start building the code.
Code which makes use of Background Worker
For the kind of scenario described above you’ll have a Windows or Web UI based application with a front end developed using either WPF, Winforms, WebForms or ASP.Net MVC. For simplicity I have created a simple console application just for demo purpose. I am not going to show any visual stuff hence I decided to use console application.
I have created a ProductPresenter which is defined as
private readonly IProductRepository productRepository;
public ProductPresenter(IProductRepository productRepository)
{
this.productRepository = productRepository;
}
public IProductListView ProductListView
{
get;
set;
}
public void GetAllProducts()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
ProductListView.DisplayHourGlass = true;
worker.RunWorkerAsync();
}
The constructor has a dependency on IProductRepository which I’ll mock using RhinoMocks during testing phase. If you look at the complete source code you’ll notice that I have not implemented the IProductRepository interface. From the point of TDD it makes sense because I am programming to an interface. I have property ProductListView which is of type IProductListView. This is also an abstraction of the view and I do not have any concrete code for ProductListView.
Then comes the method which is the point of contention for this blog post GetAllProducts. This method creates an instance of the of BackgroundWorker class available in the DotNet Framework 2.0 onwards. It assigns the delegate for the method which will run a piece of code on the worker or background thread using DoWork event. We have assigned a method named worker_DoWork. Assume that the long running process of contacting the service and fetching the data will happen in this method.
We also need to specify the callback method which will perform the UI updates once the background worker has finished its job. This is achieved by subscribing to the RunWorkerCompleted event of the background worker. Once the background thread has completed its work it calls this method back on the main thread. This can be used to update the controls on the user interface in a real life application. In my case I have abstracted the view. When the long running task is completed I hide the hourglass symbol displayed on the view.
Next question you’ll ask me is when did we show the hourglass symbol. We would do this just before starting the worker thread using the background workers RunWorkerAsync method. The background worker spawns a thread only after we call the RunWorkerAsync method.
The worker_DoWork mthod implimentation looks like
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = productRepository.GetAllProducts();
}
We use the injected product repository to make a call to GetAllProducts on the service. We assign the result back to the DoWorkEventsArgs which gets returned to the method which subscribes to the RunWorkerCompleted event. This method is defined as
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProductListView.DisplayHourGlass = false;
ProductListView.Products = (IList<Product>)e.Result;
}
The method is responsible for updating the product list on the user interface. Finally the Product is a domain object or the model which has few properties like
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int UnitPrice { get; set; }
}
Unit Test Background Worker
Lets start with the unit tests. Here is the code which mocks the dependencies using Rhino Mocks and creates an instance of the presenter class which happens to be our system under test (SUT).
private ProductPresenter presenter;
private MockRepository mocks;
private IProductListView mockView;
private IProductRepository mockRepository;
[TestInitialize]
public void Setup()
{
mocks = new MockRepository();
mockView = mocks.StrictMock<IProductListView>();
mockRepository = mocks.StrictMock<IProductRepository>();
presenter = new ProductPresenter(mockRepository) { ProductListView = mockView };
}
I have created a mock view and a mock repository. I just need to test the presenter method.
[TestMethod]
public void GetAllProductsTest()
{
IList<Product> expectedProducts = new List<Product>
{
new Product { ProductId = 1, Description = "Parle G Burboun Biscuit", Name = "Burboun Biscuit", UnitPrice = 15 },
new Product { ProductId = 2, Description = "Amul Ice Cream", Name = "Ice Cream", UnitPrice = 45 }
};
mockView.Expect(mv => mv.DisplayHourGlass).PropertyBehavior();
mockRepository.Expect(mr => mr.GetAllProducts()).Return(expectedProducts);
mockView.Expect(mv => mv.Products).PropertyBehavior();
mocks.ReplayAll();
presenter.GetAllProducts();
}
In this test I have created 2 products which the service call will return to the presenter. I am expecting a call to the DisplayHourGlass property on the view and I have mocked it by setting the property behavior. I am also setting an expectation for the GetAllProducts method to be called on the mock repository which will return me the expected products. And finally I am setting an expectation on the Products property of the view. If you execute the test now it passes as can be see from the screenshot below.
But as per the TDD rules we are missing a point here. We haven’t verified the behavior of this test. After we executed the method we should have done an Assert as part of the AAA theory. Here I can do an assert on the view’s Products property to check that the expected products and actual products match. I’ll leave it to the readers to implement it.
But the other point we are missing here is that we haven’t verified that all our expectations have been met during the execution of the test. I can do it in the Teardown method as shown below
[TestCleanup]
public void Cleanup()
{
mocks.VerifyAll();
}
calling VerifyAll on the mock repository will ensure that all the expectations have been met. Try running the test once again and you’ll most likely get the error as shown below
If you look carefully it says that ExpectationViolationException. Expected #1, Actual 0. And the expectation under consideration happens to be IProductRepository.GetAllProducts. What could be the reason for this. We have set the expectation but its not getting called.
Any guesses? This happens because of the threading. If you go back to the presenter and see which piece of code calls the productRepository.GetAllProducts method, it happens to be the one which runs on the background worker thread. In our case we are calling this code from the test runner. Because the call to DoWork method is asynchronous, our test runner does not wait for it to complete its work. The test runner continues with the next statement. And the teardown method gets invoked. In the teardown we are verifying that all the expectations have been met. It is possible that the background thread did not execute the service call in this duration and the expectation is not met. Hence we get this error while running this multi threaded code.
The simplest fix for this problem is to purposefully introduce some delay between the calling of the method on the presenter and the teardown method. This allows the background thread to complete its work and the callback method gets executed on the presenter as expected. After a short delay the teardown or cleanup is also executed. You can modify the test as shown below and rerun to see that it fixes the exception.
[TestMethod]
public void GetAllProductsTest()
{
IList<Product> expectedProducts = new List<Product>
{
new Product { ProductId = 1, Description = "Parle G Burboun Biscuit", Name = "Burboun Biscuit", UnitPrice = 15 },
new Product { ProductId = 2, Description = "Amul Ice Cream", Name = "Ice Cream", UnitPrice = 45 }
};
mockView.Expect(mv => mv.DisplayHourGlass).PropertyBehavior();
mockRepository.Expect(mr => mr.GetAllProducts()).Return(expectedProducts);
mockView.Expect(mv => mv.Products).PropertyBehavior();
mocks.ReplayAll();
presenter.GetAllProducts();
Thread.Sleep(TimeSpan.FromMilliseconds(3));
}
Conclusion
It is always difficult to unit test the multi threaded code. It took us almost a day to find out what was causing this problem. Because in the debug mode things would work fine and after removing the breakpoint we would encounter exception. After putting the test runner thread to sleep everything started working fine.
Please note that I have used a delay of 3 milliseconds. But if you have complex logic it might take you a little bit longer to execute them on the worker thread. You might need to change that value to a different one based on trail and error method.
As usual I have uploaded the complete source code to dropbox which you can download BackgroundWorkerUnitTest.
There might be a simpler way of unit testing a background worker related code. I would love to know about it. Until next time Happy programming :)
Dude....great post...keep it comin
ReplyDelete@Lohith Thanks for the compliments
ReplyDelete