I want to mock abstract class public method to returns different values. Is it doable using fakeiteasy?
public class Worker: IInterface
{
private readonly ILogger _logger;
provate readonly IServiceProvider _serviceProvider;
public Worker(ILogger logger, IServiceProvider provider)
{
_logger = logger;
_serviceProvider = provider;
}
public Handler(Order order)
{
var service = _serviceProvier.GetService(order.Properties.Type);
service.DoTaskAsync(...);
}
}
public abstract class ServiceBases
{
public async Task DoTaskAsync(...);
}
[Fact]
public async Task WorkerHandler_Return_Success()
{
// how to mock ServiceBases.DoTaskAsync to returns null or throw exception ?
}
CodePudding user response:
This is complicated because you've got two things to figure out:
- How to create a mock of your abstract class
- How to set up your
IServiceProvider
to return an instance of the class that inherits from the abstract class.
Let's start with mocking the abstract class. The easiest way to do that is to create a class that implements the abstract class. You could do this:
public class FakeServiceBases : ServiceBases
{
public int NumberOfTimesCalled { get; private set; }
public override async Task DoTaskAsync()
{
NumberOfTimesCalled ;
}
}
Presumable you're mocking this so that you can make sure the class that depends on this calls DoTaskAsync()
. If you create an instance of this fake class then when Handler
calls it, it will increment the value. When the test runs you can check that the value has been incremented.
Or maybe you want to make sure that the expected arguments were passed. I don't know what the arguments are, but when DoTaskAsync()
is called you can add the arguments to a list instead of incrementing a number, like this:
public class FakeServiceBases : ServiceBases
{
public List<SomeArgumentType> CallsToDoAsync { get; } = new List<SomeArgumentType>();
public override async Task DoTaskAsync(SomeArgumentType someArgument)
{
CallsToDoAsync.Add(someArgument);
}
}
Same thing. After calling your Handler
method you can inspect the arguments that were passed to service.DoTaskAsync
by looking at what's in the CallsToDoAsync
list.
That takes care of mocking ServiceBases
. The other step is getting your IServiceProvider
to always return a FakeServiceBase
regardless of the type passed to it.
That you can do with FakeItEasy.
var fakeServiceBase = new FakeServiceBases();
var serviceProvider = A.Fake<IServiceProvider>();
A.CallTo(() => serviceProvider.GetService(A<Type>.Ignored)).Returns(fakeServiceBase);
var testSubject = new Worker(NullLogger.Instance, serviceProvider);
This creates a faked IServiceProvider
. Whenever GetService
is called, regardless of the type passed to it, it will return the instance of FakeServiceBases
that you created. After calling methods on the Worker
you can inspect fakeServiceBase
to make sure methods were called on it like you expected.
We can make this class easier to test by removing the dependency on IServiceProvider
.
Let's change Worker
to look like this:
public class Worker
{
private readonly ILogger _logger;
private readonly Func<Type, ServiceBases> _getServicesBases;
public Worker(ILogger logger, Func<Type, ServiceBases> getServicesBases)
{
_logger = logger;
_getServicesBases = getServicesBases;
}
public async Task Handler(Order order)
{
// Calls the function - passes a Type and gets a ServiceBases
var service = _getServicesBases(order.Properties.Type);
service.DoTaskAsync(...);
}
}
Now it doesn't depend on IServiceProvider
. It depends on a Func<Type, ServiceBases>
. In other words, it depends on a function that will take a Type
and return an object that implements ServicesBases
.
When you're registering your types in your Startup
you can do this:
services.AddSingleton<Func<Type, ServiceBases>>(serviceProvider =>
new Func<Type, ServiceBases>(type => (ServiceBases)serviceProvider.GetService(type)));
So at runtime the function will actually resolve the type from the IServiceProvider
.
But now that your class depends on a function, the mock is even simpler. You don't need to mock an IServiceProvider
. Instead, you provide a "real" function that returns your FakeServiceBases
:
var fakeServiceBase = new FakeServiceBases();
Func<Type, ServiceBases> getServiceBases = type => fakeServiceBase;
var testSubject = new Worker(NullLogger.Instance, getServiceBases);