Dependency Injection with XUnit Mediatr and IServiceCollection - c#

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.

Related

MediatR error: Register your handlers with the container

I have a .Net Core app where i use the .AddMediatR extension to register the assembly for my commands and handlers
In ConfigureServices in Startup.cs i have used the extension method
from the official package
MediatR.Extensions.Microsoft.DependencyInjection with the following
parameter:
startup.cs
services.AddBLL();
DependencyInjection.cs
public static IServiceCollection AddBLL(this IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPerformanceBehaviour<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>));
return services;
}
The commandhandler class are as follow:
ListEpostaHesaplariHandler.cs
public class ListEpostaHesaplariRequest : IRequest<ResultDataDto<List<ListEpostaHesaplariDto>>>
{
public FiltreEpostaHesaplariDto Model { get; set; }
}
public class ListEpostaHesaplariHandler : IRequestHandler<ListEpostaHesaplariRequest, ResultDataDto<List<ListEpostaHesaplariDto>>>
{
private readonly IBaseDbContext _context;
private readonly IMapper _mapper;
public ListEpostaHesaplariHandler(IBaseDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<ResultDataDto<List<ListEpostaHesaplariDto>>> Handle(ListEpostaHesaplariRequest request, CancellationToken cancellationToken)
{
await Task.Delay(1);
var resultDataDto = new ResultDataDto<List<ListEpostaHesaplariDto>>() { Status = ResultStatus.Success };
try
{
var foo = _context.EpostaHesaplari;
var filtreliEpostaHesaplari = Filtre(request.Model, foo);
var epostaHesaplariDto = _mapper.Map<List<ListEpostaHesaplariDto>>(filtreliEpostaHesaplari);
resultDataDto.Result = epostaHesaplariDto;
}
catch (Exception ex)
{
resultDataDto.Status = ResultStatus.Error;
resultDataDto.AddError(ex.Message);
}
return resultDataDto;
}
This is the Controller I used the MediatR
BaseController.cs
public abstract class BaseController : Controller
{
private IMediator _mediator;
public static int? birimIDD;
protected IMediator Mediator
{
get
{
return _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
}
}
}
HomeController.cs
[HttpPost]
public async Task<IActionResult> MailGonderAsync()
{
FiltreEpostaHesaplariDto filtre = new FiltreEpostaHesaplariDto();
filtre.Durum = true;
var req = new ListEpostaHesaplariRequest() { Model = filtre };
var response = await Mediator.Send(req);
}
no problem so far await Mediator.Send(req); this response is coming successfully
the problem starts from here
send.cs (in class business logic layer )
public class EmailSend : IEmailSingleSender
{
private readonly IMediator _mediator;
public EmailSend(IMediator mediator)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
public async Task Send(EmailParamsDto emailParamsDto)
{
try
{
FiltreEpostaHesaplariDto filtre = new FiltreEpostaHesaplariDto();
filtre.Durum = true;
var req = new ListEpostaHesaplariRequest() { Model = filtre };
var response = await _mediator.Send(req);
SmtpClient smtpClient = new SmtpClient();
smtpClient.Port = _emailSetting.Port;
smtpClient.Host = _emailSetting.Server;
smtpClient.EnableSsl = _emailSetting.UseSsl;
NetworkCredential networkCredential = new NetworkCredential();
networkCredential.UserName = _emailSetting.UserName;
networkCredential.Password = _emailSetting.Password;
smtpClient.Credentials = networkCredential;
MailMessage mail = new MailMessage();
mail.IsBodyHtml = _emailSetting.IsHtml;
mail.From = new MailAddress(_emailSetting.UserName, emailParamsDto.Subject);
mail.To.Add(new MailAddress(emailParamsDto.Receiver));
//mail.CC.Add(_emailSetting.AdminMail);
mail.Body = emailParamsDto.Body;
mail.Subject = emailParamsDto.Subject;
mail.Priority = emailParamsDto.MailPriority;
await smtpClient.SendMailAsync(mail);
}
catch (Exception ex)
{
throw ex;
}
}
}
When this line, in the Send method, executes I get the exception:
var response = await _mediator.Send(req);
Error constructing handler for request of type
MediatR.IRequestHandler2[IUC.BaseApplication.BLL.Handlers.Yonetim.EpostaHesaplariHandlers.ListEpostaHesaplariRequest,IUC.BaseApplication.COMMON.Models.ResultDataDto1[System.Collections.Generic.List`1[IUC.BaseApplication.BLL.Models.Yonetim.EpostaHesaplariDto.ListEpostaHesaplariDto]]].
Register your handlers with the container. See the samples in GitHub
for examples.
services.AddMediatR(Assembly.GetExecutingAssembly());
All your handlers and commands are in this assembly you passed?
builder.Services.AddMediatR(AppDomain.CurrentDomain.GetAssemblies());
MediatR Service Registration in Dotnet 6 in program files.
The problem might be because "No parameterless constructor defined" for e.g. MappingProfiles class inherited from Profile class. And it must be public, not protected. You might not see this exception if your custom middleware hides inner exceptions.
If MediateR handler has any object injected through DI & that DI object's constructor is throwing exception, you will encounter also this error.
For example, MediateR handler has IRepository injected and object of IRepository constructor is trying to open database connection, but exception is thrown you'll encounter Error constructing handler for request of type MediatR.
public class MyHandler : IRequestHandler<Command, Result>
{
private readonly IRepository _myRepo;
private readonly IMapper _mapper;
public MyHandler(IRepository myRepo, IMapper mapper)
{
_myRepo = myRepo;
_mapper = mapper;
}
}
And Repository code is
public class MyRepository : IRepository
{
private IDbConnection _connection;
private readonly IConfiguration _configuration;
public MyRepository(IConfiguration configuration)
{
_configuration = configuration;
string connStr = _configuration.GetConnectionString("DefaultConnection")
_connection = new OracleConnection(connStr);
_connection.Open(); // if this line throws exception
}
}
_connection.Open(); will cause the Error constructing handler for request of type MediatR. if exception is thrown while opening the connection

.NET Core Console App | Hangfire With Dependency Injection

Goal:
Fundamentally I am trying to add a background job, that has dependencies injected, to a console application.
Problem:
Although the jobs are queued, they are never executed.
Program.cs
var appSettings = ConfigHelper.GetConfig();
Console.WriteLine("Initialising Hangfire Server...");
GlobalConfiguration.Configuration.UseSqlServerStorage(appSettings.ConnectionString);
using (var server = new BackgroundJobServer())
{
Console.WriteLine("Hangfire Server started.");
var t = serviceProvider.GetService<ITestService>();
t.Test();
Console.ReadKey();
}
ServiceProviderFactory.cs
public static void Setup()
{
IServiceCollection services = new ServiceCollection();
...
services.AddDbContext<Db>(x => x.UseSqlServer(appSettings.ConnectionString));
services.AddTransient<IInsertLogJob, InsertLogJob>();
services.AddTransient<ITestService, TestService>();
_serviceProvider = services.BuildServiceProvider();
}
TestService.cs
public interface ITestService
{
void Test();
}
public class TestService : ITestService
{
private readonly ILogger<TestService> _logger;
public TestService(ILogger<TestService> logger)
{
_logger = logger;
}
public void Test()
{
logger.LogInformation("Test");
_logger.LogError("Error");
}
}
Logger.cs
public class Logger : ILogger
{
...
Log log = new Log()
{
Message = message,
EventId = eventId.Id,
ObjectId = eventId.Name,
LogLevel = logLevel.ToString(),
CreatedTime = DateTime.Now
};
BackgroundJob.Enqueue<IInsertLogJob>(j => j.Insert(log));
}
InsertLogJob.cs
public interface IInsertLogJob
{
void Insert(Log log);
}
public class InsertLogJob : IInsertLogJob
{
private Db _dataContext;
public InsertLogJob(Db context)
{
_dataContext = context;
}
public void Insert(Log log)//<-- this never happens
{
_dataContext.Logs.Add(log);
_dataContext.SaveChanges();
}
}
DB Record
So all the code up to the point where the data has to be inserted into the database runs, the Hangfire job gets inserted as per the picture above, but the code is never executed.

I want to Create Xunit test for this controller. How can i do that

I have created small kind of xunit test case but I don't know how to create this controller which i have mention below.
public class PropertyController : ControllerBase
{
private readonly IMediator _mediator;
private readonly ILogger<PropertyController> _logger;
public PropertyController(IMediator mediator, ILogger<PropertyController> logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<IActionResult> AddProperty([FromBody] AddPropertyCommand command)
{
bool commandResult = false;
_logger.LogInformation(
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({#Command})",
command.GetGenericTypeName(),
nameof(command.ModifiedUserId),
command.ModifiedUserId,
command);
commandResult = await _mediator.Send(command);
if (!commandResult)
{
return BadRequest();
}
return Ok();
}
I have created like this. i have mock the dependency and create a test case for add command is working fine or not
public class PropertyControllerTest
{
private readonly PropertyController _it;
private readonly Mock<IMediator> _mediatorMock;
private readonly Mock<ILogger<PropertyController>> _loggerPropertycontrollerMock;
public PropertyControllerTest()
{
_mediatorMock = new Mock<IMediator>();
_loggerPropertycontrollerMock = new Mock<ILogger<PropertyController>>();
_it = new PropertyController(_mediatorMock.Object, _loggerPropertycontrollerMock.Object);
}
[Fact]
public void it_Should_add_information_successfully_and_returns_200_status_result()
{
//How can i write xunit test case. I'm creating like this
_mediatorMock.Setup(x => x.Send().Returns(property);
}
The test below covers the 200 status result - a similar test for bad requests would be very similar.
[Fact]
public void it_Should_add_information_successfully_and_returns_200_status_result()
{
// Arrange
var expected = new AddPropertyCommand();
_mediatorMock.Setup(x => x.Send(It.IsAny<AddPropertyCommand>())).Returns(true);
// Act
var actionResult = _it.AddProperty(expected);
// Assert
actionResult.ShouldBeAssignableTo<OkResult>();
_mediatorMock.Verify(x => x.Send(expected));
}
N.B. actionResult.ShouldBeAssignableTo<OkResult>(); is written using the Shouldly assertion framework, you can swap that out for anything you like. The one built into XUnit would be like this: Assert.IsType(typeof(OkResult), actionResult);

unit test base controller with ILogger

I have a base controller (i didn't create it btw) in my net core api that basically starts with following:
public abstract class MyBaseController<T> : ControllerBase where T : MyBaseController<T>
{
private ILogger<T> _logger;
protected ILogger<T> Logger => _logger ?? (_logger = HttpContext?.RequestServices.GetService<ILogger<T>>());
}
When i am unit testing my other controller that inherits the base controller how do deal with this logger?
currently my unit test class has a constructer with something like
_controller = new cartController(_cartService);
but then i get stuck.
I will be using xUnit and Moq in the test project.
Any help is appreciated.Thanks
Here's a minimal example from this article on how to inject an ILogger dependency and then verify a call afterward with Moq:
public class LogTest
{
private readonly ILogger _logger;
public const string InformationMessage = "Test message";
public const string ErrorMessage = "Not implemented {recordId}";
public LogTest(ILogger<LogTest> logger)
{
_logger = logger;
}
public void Process()
{
_logger.LogInformation(InformationMessage);
}
}
_loggerMock.Verify(l => l.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Exactly(1));
In general you should rely on DI in tests as well as in runtime. The following library contains a test logger which you may use in tests: https://www.nuget.org/packages/com.github.akovac35.Logging.Testing/
Usage samples are available here: https://github.com/akovac35/Logging.Samples
Disclaimer: I am the author of the above.
Basically you would proceed as follows:
Use NullLogger by default:
public abstract class MyBaseController<T> : ControllerBase
{
private ILogger _logger = NullLogger.Instance;
protected MyBaseController(ILogger<MyBaseController<T>> logger = null)
{
if (logger != null) _logger = logger;
}
}
Derived classes should inject logger:
public class MyBaseControllerVariant<T> : MyBaseController<T>
{
private ILogger _logger = NullLogger.Instance;
public MyBaseControllerVariant(ILogger<MyBaseControllerVariant<T>> logger = null, ILogger<MyBaseController<T>> baseLogger = null): base(baseLogger)
{
if (logger != null) _logger = logger;
}
}
Now wire up everything:
using com.github.akovac35.Logging.Testing;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Shared.Mocks;
using System;
namespace TestApp
{
[TestFixture]
public class TestLoggingExamples
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
customOnWrite = writeContext => {
Console.WriteLine(writeContext);
};
customOnBeginScope = scopeContext => {
Console.WriteLine(scopeContext);
};
serviceCollection = new ServiceCollection();
serviceCollection.AddTransient(typeof(MyBaseControllerVariant<>));
// Register TestLogger using extension method
serviceCollection.AddTestLogger(onWrite: customOnWrite, onBeginScope: customOnBeginScope);
}
private IServiceCollection serviceCollection;
private Action<WriteContext> customOnWrite;
private Action<ScopeContext> customOnBeginScope;
[Test]
public void Test_WithLoggingToTestConsole_Works()
{
// The service provider should be defined on per-test level or logger writes will accumulate and may result in OOM - clean them with testSink.Clear()
var serviceProvider = serviceCollection.BuildServiceProvider();
var controller = serviceProvider.GetRequiredService<MyBaseControllerVariant<object>>();
controller.Invoke();
var testSink = serviceProvider.GetRequiredService<ITestSink>();
Assert.IsTrue(testSink.Writes.Count > 0);
Assert.IsTrue(testSink.Scopes.Count > 0);
}
}
}

EF 7 (Core). Create DBContext like AddTransient

According to documents when I configure DbContext like below DI register it in scope (per http request)
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
The problem appears when I am trying to access it in another thread.
public class HomeController : Controller
{
private readonly DBData _context;
public HomeController(DBData context)
{
_context = context;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}
I want to configure DbContext creation per each call (AddTransition). It would give me possibility to write next code
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
//somehow configure it to use AddTransient
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
services.AddTransient<IUnitOfWorkFactoryPerCall, UnitOfWorkFactory>();
services.AddScoped<IUnitOfWorkFactoryPerRequest, UnitOfWorkFactory>();
services.AddMvc();
}
public interface IUnitOfWorkFactoryPerCall : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactoryPerRequest : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactory : IDisposable
{
DBData Context { get; }
}
public class UnitOfWorkFactory : IUnitOfWorkFactoryPerCall, IUnitOfWorkFactoryPerRequest
{
public UnitOfWorkFactory(DBData context)
{
Context = context;
}
public DBData Context
{
get; private set;
}
public void Dispose()
{
Context.Dispose();
}
}
So now if I want to create DBContext per request I will use IUnitOfWorkFactoryPerRequest, and when I want to use DBContext in some background thread I can use IUnitOfWorkFactoryPerCall.
My temporary solution.
I created singleton which can create Context "in transient way"
public class AppDependencyResolver
{
private static AppDependencyResolver _resolver;
public static AppDependencyResolver Current
{
get
{
if (_resolver == null)
throw new Exception("AppDependencyResolver not initialized. You should initialize it in Startup class");
return _resolver;
}
}
public static void Init(IServiceProvider services)
{
_resolver = new AppDependencyResolver(services);
}
private readonly IServiceProvider _serviceProvider;
public AppDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IUnitOfWorkFactory CreateUoWinCurrentThread()
{
var scopeResolver = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
return new UnitOfWorkFactory(scopeResolver.ServiceProvider.GetRequiredService<DBData>(), scopeResolver);
}
}
Then I call init method in Startup Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
AppDependencyResolver.Init(app.ApplicationServices);
//other configure code
}
And after all I can call AppDependencyResolver.Current.CreateUoWinCurrentThread() in some background thread.
If someone can provide more elegant solution I will be appreciated.
Within your controller, why are you trying to inject into private readonly DBData _context;? If you've registered your IUnitOfWorkFactoryPerCall via DI, you should be injecting that into your controller I believe? You then access your context via the interface.
To expand, this is what I am suggesting you do:
public class HomeController : Controller
{
private readonly IUnitOfWorkFactoryPerCall _contextFactory;
public HomeController(IUnitOfWorkFactoryPerCall contextFactory)
{
_contextFactory = contextFactory;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _contextFactory.Context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}

Categories

Resources