Currently I'm able to handle IServiceCollection to inject mocks for particular services in the following manner.
public class TestClass
{
private IMediator _mediatr;
private void SetupProvider(IUnitOfWork unitOfWork, ILogger logger)
{
configuration = new ConfigurationBuilder().Build();
_services = new ServiceCollection();
_services.AddSingleton(configuration);
_services.AddScoped(x => unitOfWork);
_services.AddSingleton(logger);
_services.AddMediatR(Assembly.Load("Application"));
_services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggerBehaviour<,>));
_mediator = _services.BuildServiceProvider().GetService<IMediator>();
}
[Fact]
public async void UnitTest_Success()
{
var unitOfWork = new Mock<IUnitOfWork>();
var logger = new Mock<ILogger>();
SetupProvider(unitOfWork.Object, logger.Object);
var fixture = new Fixture();
var command = fixture.Create<MediatorCommand>();
unitOfWork.Setup(x => x.Repository.FindAll(It.IsAny<IList<long>>(), It.IsAny<bool?>()))
.ReturnsAsync(new List<Domain.Model>());
var response = await _mediatr.Send(command);
using (new AssertionScope())
{
response.Should().NotBeNull();
response.IsSuccess.Should().BeTrue();
}
}
}
For the following subject under test
public class MediatorCommand : IRequest<CommandResponse>
{
public string Name { get; set ;}
public string Address { get; set; }
}
public class MediatorCommandHandler : IRequestHandler<MediatorCommand, CommandResponse>
{
private readonly ILogger _logger;
private readonly IUnitOfWork _unitOfWork;
public MediatorCommandHandler(IUnitOfWork unitOfWork, ILogger logger)
{
_logger = logger;
_unitOfWork = unitOfWork;
}
public async Task<CommandResponse> Handle(MediatorCommand command, CancellationToken cancellationToken)
{
var result = new CommandResponse { IsSuccess = false };
try
{
var entity = GetEntityFromCommand(command);
await _unitOfWork.Save(entity);
result.IsSuccess = true;
}
catch(Exception ex)
{
_logger.LogError(ex, ex.Message);
}
return result;
}
}
This test runs fine and the unitOfWork and logger mocks are used in the command handlers.
I'm try to move this so that the IServiceCollection construction happens per class instead of each test using the following:
public class SetupFixture : IDisposable
{
public IServiceCollection _services;
public IMediator Mediator { get; private set; }
public Mock<IUnitOfWork> UnitOfWork { get; private set; }
public SetupFixtureBase()
{
UnitOfWork = new Mock<IUnitOfWork>();
configuration = new ConfigurationBuilder().Build();
_services = new ServiceCollection();
_services.AddSingleton(configuration);
_services.AddScoped(x => UnitOfWork);
_services.AddSingleton(new Mock<ILogger>().Object);
_services.AddMediatR(Assembly.Load("Application"));
_services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggerBehaviour<,>));
Mediator = _services.BuildServiceProvider().GetService<IMediator>();
}
public void Dispose()
{
Mediator = null;
_services.Clear();
_services = null;
}
}
public class TestClass : IClassFixture<SetupFixture>
{
protected readonly SetupFixture _setupFixture;
public UnitTestBase(SetupFixture setupFixture)
{
_setupFixture = setupFixture;
}
[Fact]
public async void UnitTest_Success()
{
var fixture = new Fixture();
var command = fixture.Create<MediatorCommand>();
_setupFixture.UnitOfWork.Setup(x => x.Repository.FindAll(It.IsAny<IList<long>>(), It.IsAny<bool?>()))
.ReturnsAsync(new List<Domain.Model>());
var response = await _mediatr.Send(command);
using (new AssertionScope())
{
response.Should().NotBeNull();
response.IsSuccess.Should().BeTrue();
}
}
}
Unfortunately with this method my mocks do not get injected on the command handler. Is there a way to get this to work?
Thank you,
I found the issue and it is not related to moving to IClassFixuture<>. The issue was that I was initializing Mediator on a base class an then adding the mock UnitOfWork on a derived class.
This cause the Mediator initialization to fail because one of the beheviours expected the UnitOfWork which at the time was not yet on the container.
Moving the initialization of Mediator after all the services have been added helped me resolve the issue and now all works as expected.
If you try the same thing, please make sure to include all the services in the container before initializing any objects that require those dependencies.
Thank you all those who had input.