I wrote some unit tests today (to see if I still feel) and I found a pattern that I believe I will use in the future as well. The problem was that I was testing a class that had multiple services injected in the constructor. Imagine a BigService that needs to receive instances of SmallService1, SmallService2 and SmallService3 in the constructor. In reality there will be more, but I am trying to keep this short. While a dependency injection framework handles this for the code, for unit tests these dependencies must be mocked.
C# Example (using the Moq framework for .NET):
// setup mock for SmallService1 which implements the ISmallService1 interface
var smallService1Mock = new Mock<ISmallService1>();
smallService1Mock
.Setup(srv=>srv.SomeMethod)
.Returns("some result");
var smallService1 = smallService1Mock.Object.
// repeat for 2 and 3
var bigService = new BigService(smallService1, smallService2, smallService3);

My desire was to keep tests separate. I didn't want to have methods that populated fields in the test class so that if they run in parallel, it wouldn't be a problem. But still, I didn't want to copy paste the same test a few times only to change some parameter or another. And the many local variables for mocks and object really bothered me. Moreover, there were some operations that I really wanted in all my tests, like a mock of a service that executed an action that I was giving it as a parameter, with some extra work before and after. I wanted that in all test cases, the mock would just execute the action.

Therefore I got to writing something like this:
public class BigServiceTester {
public Mock<ISmallService1> SmallService1Mock {get;private set;}
public Mock<ISmallService2> SmallService2Mock {get;private set;}
public Mock<ISmallService3> SmallService3Mock {get;private set;}
 
public ISmallService1 SmallService1 => SmallService1Mock.Object;
public ISmallService2 SmallService2 => SmallService2Mock.Object;
public ISmallService3 SmallService3 => SmallService3Mock.Object;
 
public void BigServiceTester() {
SmallService1Mock = new Mock<ISmallService1>();
SmallService2Mock = new Mock<ISmallService2>();
SmallService3Mock = new Mock<ISmallService3>();
}
 
public BigServiceTester SetupDefaultSmallService1() {
SmallService1Mock
.Setup(ss1=>ss1.Execute(It.IsAny<Action>()))
.Callback<Action>(a=>a());
return this;
}
 
public IBigService GetService() =>
new BigService(SmallService1, SmallService2, SmallService3);
}
 
// and here is how to use it in an XUnit test
[Fact]
public void BigServiceShouldDoStuff() {
var pars = new BigServiceTester()
.SetupDefaultSmallService1();
 
pars.SmallService2Mock
.Setup(ss2=>ss2.SomeMethod())
.Returns("some value");
 
var bigService=pars.GetService();
var stuff = bigService.DoStuff();
Assert.Equal("expected value", stuff);
}

The idea is that everything related to BigService is encapsulated in BigServiceTester. Tests will be small because one only creates a new instance of tester, then sets up code specific to the test, then the tester will also instantiate the service, followed by the asserts. Since the setup code and the asserts depend on the specific test, everything else is reusable, but also encapsulated. There is no setup code in the constructor but a fluent interface is used to easily execute common solutions.

As a further step, tester classes can implement interfaces, so for example if some other class needs an instance of ISmallService1, all it has to do is implement INeedsSmallService1 and the SetupDefaultSmallService1 method can be written as an extension method. I kind of like this way of doing things. I hope you do, too.

Comments

Be the first to post a comment

Post a comment