Testing multithreading service methods in mocks
For example I want to test the fact that my multi-threaded method is calling the repository methods n times if I give him n chunks of data from different threads. Of course mocks are not thread safe and even are not supposed to be.
[Test]
pu开发者_开发知识库blic void CanSaveCustomersInParallel()
{
var customers = new List<List<Customer>>
{
new List<Customer>
{
new Customer {FirstName = "FirstName1"},
new Customer {FirstName = "FirstName2"}
},
new List<Customer>
{
new Customer {FirstName = "FirstName3"},
new Customer {FirstName = "FirstName4"}
}
};
_serviceCustomers.ParallelSaveBatch(customers);
_repoCustomers
.Verify(x => x.SaveBatch(It.IsAny<List<Customer>>()), Times.Exactly(2));
}
Of course, this test fails sometimes and sometimes it does not. But it is incorrect in its essence. Can you advise me how to re-write it?
Well, the next stub did the trick:
internal class ServiceStub: Service<DummyEntity>
{
private int _count;
public int Count
{
get { return _count; }
}
public override void SaveBatch(IEnumerable<object> entities)
{
lock(this)
{
_count++;
}
}
public ServiceStub(IRepository<DummyEntity> repository):base(repository)
{
_count = 0;
}
}
And the unit test looks the next way:
[Test]
public void CanSaveCustomersInParallel()
{
var service = new ServiceStub(new DummyRepository());
var customers = new List<List<Customer>>
{
new List<Customer>
{
new Customer {FirstName = "FirstName1"},
new Customer {FirstName = "FirstName2"}
},
new List<Customer>
{
new Customer {FirstName = "FirstName3"},
new Customer {FirstName = "FirstName4"}
}
};
service.ParallelSaveBatch(customers);
Assert.AreEqual(service.Count, customers.Count);
}
You might be interested to know that as of v 4.1, Moq now does have much improved thread safety, and you should find tests such as yours, when run in parallel, should now verify as expected.
More on the erratic Moq behaviour here
Typical issues encountered in versions <= 4.0 included random NullReferenceException
or IndexOutOfRangeException
, and mock.Verify(<>, Times.Exactly(N))
failing (usually with an undercount). As of 4.1, these issues now appear to be fixed MacGyvered, ~thanks to the community!
Edit As per @Danny's comment below, note that the changes made in 4.1 included locking the Mock, which isn't much use if you need to test the parallelism of your code.
A design change can simplify this test. Create a SaveWorker that does the actual work, and a proxy that does the work of a SaveWork on another thread (same abstraction). Then a SaveWorkerFactory that returns a ThreadedSaveWorker, given a customer. Finally, inject a Mock of SaveWorkerFactory into _serviceCustomers, and verify that it does the 2 calls.
精彩评论