Recently I was asked about how to test a scenario in a unit test where a call to a dependent service could change the value of the instance passed through to it. Potentially the service being called could change a setting and the subsequent usages of the instance being passed around might drive some further logic.
Below is a simple scenario I put together to show the situation.
public class Service
{
private readonly ISubService _subService;
public Service(ISubService subService)
{
_subService = subService;
}
public void Run()
{
Context context = new Context
{
// being explicit for demo purposes
Throw = false
};
// call the service
_subService.Update(context);
if (context.Throw)
{
throw new Exception();
}
}
}
The call to the subservice could change the context, add something, remove something or change the value of it and then subsequent code will need that value. In this demo example the call to the sub service could change the value of Context.Throw
. If the value of the Throw
property is true it will then throw an exception later in the service being tested.
So how do we test this?
Let’s first start by setting up the basics of the unit test.
[Fact]
public void Throw_Set_On_Context_Causes_Exception()
{
// Arrange
Mock<ISubService> subService = new Mock<ISubService>();
var sut = new Service(subService.Object);
// Act
Action result = () => sut.Run();
// Assert
result.Should().Throw<Exception>();
}
Using the mocking library Moq we can create a mock of ISubService
and pass that into the constructor of the service we are trying to test, the “System Under Test” or “SUT” for short.
This test will now fail. How do we drive the conditional logic using the mock?
This is where the power of the Callback
functionality comes into play when setting up the expectation on the mocked dependency.
subService.Setup(x => x.Update(It.IsAny<Context>())).Callback<Context>(ctx => ctx.Throw = true);
We first setup what we would like the mock service to do once the Update
method is called. If no expectations are setup on the mock it won’t do anything, as simple as that.
We then specify that any instance can be passed into the Update
method. We do not mind what instance is passed into the method. The Callback
method is driven by the same types as the parameters of the method which is being mocked. We can define an Action<Context>
, which is the types of the signature again, and get access to the value passed into the method when it’s called in the service. Once we have access to it in the “callback” we can then set the value to true
.
Now we can run the updated test and it passes.
Let’s take a look at the complete test in full.
[Fact]
public void Throw_Set_On_Context_Causes_Exception()
{
// Arrange
Mock<ISubService> subService = new Mock<ISubService>();
subService.Setup(x => x.Update(It.IsAny<Context>())).Callback<Context>(ctx => ctx.Throw = true);
var sut = new Service(subService.Object);
// Act
Action result = () => sut.Run();
// Assert
result.Should().Throw<Exception>();
}
In this post we have looked at how to use the callback feature of a Moq mock object to drive conditional logic in the consuming service and validate the logic. The Callback
functionality is very powerful and can also be used to inspect the parameters which are passed into the method.
Any questions/comments then please contact me on Twitter @WestDiscGolf