IClassFixture called multipletimes with locked Simple Injector Container - c#

i'm trying to test my controllers with IClassFixture<WebApplicationFactory<Startup>>, but when i run multiples tests i get the error:
System.InvalidOperationException : The container can't be changed
after the first call to GetInstance, GetAllInstances, Verify, and some
calls of GetRegistration. Please see https://simpleinjector.org/locked
to understand why the container is locked. The following stack trace
describes the location where the container was locked:
public class TestControllerTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly HttpClient _httpClient;
private readonly TestDbContext _dbContext;
private readonly AuthenticationClientBuilder<MicrosoftPatternAdministratorAuthHandler, Startup> _builder;
private readonly WebApplicationFactory<Startup> _factory;
public LicenseControllerTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
_builder = new AuthenticationClientBuilder<MicrosoftPatternAdministratorAuthHandler, Startup>();
_dbContext = new TestDbContext(new DbContextOptions<TestDbContext>());
_dbContext.Database.SetConnectionString(_builder.GetConnectionString());
DbInitializer.Initialize(_dbContext);
_httpClient = _builder.BuildAuthenticatedClient(_factory);
}
}
In the callstack, i see that the error ocourred in the line: _httpClient = _builder.BuildAuthenticatedClient(_factory);
The code of this class is:
namespace Namespace_X
{
public class AuthenticationClientBuilder<TAuthenticationHandler, TStartup> : IDisposable
where TAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
where TStartup : class
{
private WebApplicationFactory<TStartup> _factory;
private readonly string _connectionString;
public AuthenticationClientBuilder()
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
_connectionString = config["AppSettings:ConnectionString"];
}
public HttpClient BuildAuthenticatedClient(WebApplicationFactory<TStartup> factory)
{
_factory = factory;
return _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication("TestAuthentication")
.AddScheme<AuthenticationSchemeOptions, TAuthenticationHandler>("TestAuthentication", null);
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(TestDbContext));
if (descriptor != null)
{
services.Remove(descriptor);
services.AddDbContext<TestDbContext>((options, context) =>
{
context.UseSqlServer(_connectionString);
});
}
});
}).CreateClient();
}
public string GetConnectionString()
{
return _connectionString;
}
public void Dispose()
{
_factory.Dispose();
}
}
}
In the startup the exception is throw when the container try to register the DbContext:
container.Register(() =>
{
var options = new DbContextOptionsBuilder<TestDbContext>().UseSqlServer().Options;
return new TestDbContext(options);
}, Lifestyle.Transient);
When i run one test per time they work.
Any hint? Thx in advance

Related

AddDbContext conditional by Request-Parameters

I´ve got a configuration-class which holds database connection-strings per mandant. But only with the requests parameters the mandant will be clear. So I want to inject the right DbContext contitional by mandant.
So far I´ve the following problem:
public class MessageController : IMessageController
{
private readonly IMessageParser _parser;
private readonly ILogger _log;
private readonly IMessageProcessor _receiver;
public MessageController(IMessageParser parser, IMessageProcessor receiver, ILogger log)
{
_parser = parser;
_log = log;
_receiver = receiver;
}
public async Task<Response> MessageReceivedEvent(Request request)
{
if (!_parser.TryParseMessage(request.SomeInlineData, out var mandant))
{
_log.LogError("The given Message could not be parsed");
throw new InvalidOperationException("The given Message could not be parsed");
}
// what to do with the mandant?
_receive.Received(request);
return new Response();
}
}
The receiver may has the following logic:
public class MessageProcessor : IMessageProcessor
{
// this database should be injected dependend on the current mandant
private readonly DbContext _database;
public MessageProcessor(DbContext database)
{
_database = database;
}
public void Received(Request request)
{
// Do fancy stuff
_database.SaveChanges();
}
}
Now here the ConfigureServices:
services.AddDbContext<DbContextX>((provider, options) => options.UseSqlite($"Data
Source={Path.GetFullPath("How to get the right mandant connection string?")}"))
.Configure<MandantConfiguration>(Configuration.GetSection(nameof(MandantConfiguration)))
Here the configuration class:
public class MandantConnection : IMandantConnection
{
public string DatabaseConnection { get; set; }
}
public class MandantConfiguration : IMandantConfiguration
{
public Dictionary<Mandant, MandantConnection> Mandants { get; set; }
}
EDIT:
The DbContext is injected as Scoped, so I think it should be possible to change the Connection-String per Scope but I don´t know how.
The trick is to use the HttpContext within the request.
So far here my solution for the given Problem:
public class MessageController : IMessageController
{
private readonly IMessageParser _parser;
private readonly ILogger _log;
private readonly IMessageProcessor _receiver;
private readonly IHttpContextAccessor _accessor;
public MessageController(IMessageParser parser, IMessageProcessor receiver, ILogger log, IHttpContextAccessor accessor)
{
_parser = parser;
_log = log;
_receiver = receiver;
_accessor = accessor;
}
public async Task<Response> MessageReceivedEvent(Request request)
{
if (!_parser.TryParseMessage(request.SomeInlineData, out var mandant))
{
_log.LogError("The given Message could not be parsed");
throw new InvalidOperationException("The given Message could not be parsed");
}
// ---> Thats to do
_accessor.HttpContext.Items[nameof(Mandant)] = mandant;
_receive.Received(request);
return new Response();
}
}
Then I´ve implemented a MandantService, which injects the Accessor also:
public class MandantenService : IMandantenService
{
public IMandantConnection CurrentConfiguration { get; set; }
private readonly MandantConfiguration _configuration;
public MandantenService(IOptions<MandantConfiguration> options, IHttpContextAccessor accessor)
{
_configuration = options.Value;
CurrentConfiguration = _configuration.Mandants[Enum.Parse<Mandant>(accessor.HttpContext.Items[nameof(Mandant)].ToString())];
}
}
Then I can use this service within the DbContext:
public VdvKaDbContext(DbContextOptions<VdvKaDbContext> options, IMandantenService mandantenService)
: base(options)
{
_mandantenService = mandantenService;
...
}
And configure the Sqlite Database in the OnConfigure-Method:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite($"Data Source={Path.GetFullPath(_mandantenService.CurrentConfiguration.DatabaseConnection)}");
}
And boom every call of scoped database will be the specific mandant database:
using var scope = _provider.CreateScope();
return scope.ServiceProvider.GetService<DbContext>();

Typed HTTP client and CustomWebApplicationFactory

We are using .NET Core 3.1 to develop a REST API service. We would like to implement integration tests. We found this article which explains how to use WebApplicationFactory.
CustomWebApplicationFactory.cs
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((context, conf) =>
{
var p = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.test.json");
conf.AddJsonFile(p);
});
builder.ConfigureServices(services =>
{
var configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>();
services.AddSingleton<RestApiConfigurationClient>(configuration.GetSection("RestApi").Get<RestApiConfigurationClient>());
services.AddHttpClient<RestApiHttpClient>();
});
}
}
RestApiTestBase.cs
public class RestApiTestBase
{
private readonly RestApiHttpClient _restApiHttpClient;
protected RestApiTestBase(CustomWebApplicationFactory<Rest.Server.Startup> factory)
{
var scope = factory.Services.CreateScope();
_restApiHttpClient = scope.ServiceProvider.GetRequiredService<RestApiHttpClient>();
}
}
[CollectionDefinition("RestApi_test_collection")]
public class RestApiTestCollection : ICollectionFixture<CustomWebApplicationFactory<Rest.Server.Startup>>
{
}
RestApiHttpClient.cs
public class RestApiHttpClient : IProductsService
{
private readonly HttpClient _httpClient;
private readonly IProductsService _productsService;
public RestApiHttpClient(HttpClient httpClient, RestApiConfigurationClient configuration)
{
if (configuration == null)
{
throw new Exception("Configuration is not provided");
}
httpClient.BaseAddress = new Uri(configuration.URL);
httpClient.SetAuthorizationHeader(configuration.Username, configuration.Password);
httpClient.Timeout = TimeSpan.FromMilliseconds(configuration.TimeoutMs);
_httpClient = httpClient;
_productsService = new ProductsService(this);
}
public HttpClient GetHttpClient()
{
return _httpClient;
}
public async Task<GetProductByIdResponse> GetProductById(int id)
{
return await _productsService.GetProductById(id);
}
}
ProductsService.cs
public class ProductsService : IProductsService
{
private readonly HttpClient _httpClient;
public ProductsService(RestApiHttpClient httpClient)
{
_httpClient = httpClient.GetHttpClient();
}
public async Task<GetProductByIdResponse> GetProductById(int id)
{
var response = await _httpClient.GetAsync($"Products/{id}");
return JsonConvert.DeserializeObject<GetProductByIdResponse>(await response.Content.ReadAsStringAsync());
}
}
How can we inject HttpClient which can be created by WebApplicationFactory<TEntryPoint>.CreateClient() into typed HTTP client RestApiHttpClient?

How to provide a mock interface to Autofac in AspNet Core integration testing?

I have a web api project that I'm trying to integration test. The controller is:
[Route("hello")]
public sealed class MyController : Controller
{
private readonly IFoo _foo;
public MyController(IFoo foo)
{
_foo = foo;
}
[HttpGet]
public IActionResult Hello()
{
_foo.MethodToVerify();
return Ok("Foo");
}
}
IFoo is registered in the WebApi using Autofac in ContainerBuilder (builder) like
public void Load(ContainerBuilder builder)
{
builder.RegisterType<FooImplementation>()
.WithAnotherInterface(builder, "hello")
.As<IFoo>();
}
Where WithAnotherInterface(string) is an extension method like:
public static IRegistrationBuilder<TLimit, TReflectionActivatorData, TStyle> WithAnotherInterface<TLimit, TReflectionActivatorData, TStyle>(this IRegistrationBuilder<TLimit, TReflectionActivatorData, TStyle> reg, ContainerBuilder builder, string val)
: where TReflectionActivatorData : ReflectionActivatorData
{
builder.RegisterType<AnotherInterfaceImplementation>()
.Named<IAnotherInterface>("another")
.WithParameter(ResolvedParameter.ForNamed<IErrorHandler>(val))
.AutoActivate()
.SingleInstance();
return registration;
}
Now in my test, I'm doing this:
public class MyTest : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly IFoo _foo;
private readonly HttpClient _client;
private readonly WebApplicationFactory<Startup> _factory;
MyTest(WebApplicationFactory<Startup> factory)
{
_foo = Mock.Of<IFoo>();
_factory = factory;
_client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestContainer<ContainerBuilder>(b =>
{
b.Register(ctx => _foo).SingleInstance().As<IFoo>();
});
})
.CreateClient();
}
[Fact]
public async Task Test()
{
var res = await _client.GetAsync("hello");
var mock = Mock.Get(_foo);
mock.Verify(v => v.MethodToVerify(), Times.Once);
}
}
However, my verification is failing and recorded performed invocations are zero.

Dependency Injection with XUnit Mediatr and IServiceCollection

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.

How i can inject context from UnitOfWork using StructureMap

Hi i have mi project in MVC5, i am using Identity 2.0, commonRepository and Structuremap to inject dependencies, the problem is when I am in the controller AccountController, i have one Contex and when my UnitOfWork inject the repositories it create other Instance.
how I can inject or replace the context of the identity whit my context from my UnitOfWork.
Regards
Update
AccountController
public class AccountController : Controller
{
private readonly ApplicationSignInManager SignInManager;
private readonly ApplicationUserManager UserManager;
private readonly IAuthenticationManager AuthenticationManager;
// private readonly IUbicationDao _ubicationDao;
private readonly ICultureDao _cultureDao;
private readonly ICurrencyDao _currecieDao;
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager, ICultureDao cultureDao, ICurrencyDao currecieDao, IAuthenticationManager authenticationManager)
{
UserManager = userManager;
SignInManager = signInManager;
// _ubicationDao = ubicationDao;
_cultureDao = cultureDao;
_currecieDao = currecieDao;
AuthenticationManager = authenticationManager;
}}
DefaultRegistry StructureMap
public class DefaultRegistry : Registry {
#region Constructors and Destructors
public static IList<string> Assemblies
{
get
{
return new List<string>
{
"Interfaz",
"Persistencia"
};
}
}
public static IList<Tuple<string, string>> ManuallyWired
{
get
{
return new List<Tuple<string, string>>()
{
Tuple.Create("IUserStore<ApplicationUser>", "UserStore<ApplicationUser>>"),
Tuple.Create("DbContext", "ApplicationDbContext"),
Tuple.Create("IAuthenticationManager", "HttpContext.Current.GetOwinContext().Authentication"),
};
}
}
public DefaultRegistry()
{
Scan(
scan =>
{
foreach (var assembly in Assemblies)
{
scan.Assembly(assembly);
}
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.With(new ControllerConvention());
});
For<IUserStore<ApplicationUser>>().Use<UserStore<ApplicationUser>>();
For<DbContext>().Use<ApplicationDbContext>(new ApplicationDbContext());
For<IAuthenticationManager>().Use(() => HttpContext.Current.GetOwinContext().Authentication);
//DAos
For<ICultureDao>().Use<CultureDao>();
For<ICurrencyDao>().Use<CurrencyDao>();
For<IUbicationDao>().Use<UbicationDao>();
For<IActivatorWrapper>().Use<ActivatorWrapper>();
For<IUnitOfWorkHelper>().Use<UnitOfWorkHelper>();
}
#endregion
}
UnitofWork
public class UnitOfWorkHelper : IUnitOfWorkHelper
{
private ApplicationDbContext _sessionContext;
public event EventHandler<ObjectCreatedEventArgs> ObjectCreated;
public IApplicationDbContext DBContext
{
get
{
if (_sessionContext == null)
{
_sessionContext = new ApplicationDbContext();
((IObjectContextAdapter)_sessionContext).ObjectContext.ObjectMaterialized += (sender, e) => OnObjectCreated(e.Entity);
}
return _sessionContext;
}
}
private void OnObjectCreated(object entity)
{
if (ObjectCreated != null)
ObjectCreated(this, new ObjectCreatedEventArgs(entity));
}
public void SaveChanges()
{
this.DBContext.SaveChanges();
}
public void RollBack()
{
if (_sessionContext != null)
_sessionContext.ChangeTracker.Entries()
.ToList()
.ForEach(entry => entry.State = EntityState.Unchanged);
}
public void Dispose()
{
if (_sessionContext != null)
_sessionContext.Dispose();
}
}
after a lot analyzing and understand, I finally find the solution,
first i have to inject the same context to avoid inject a new instance of the Context. the solution is:
For<DbContext>().Use(()=>System.Web.HttpContext.Current.GetOwinContext().Get<ApplicationDbContext>());
before i was injecting and add a new instance of the DBContex.
For<DbContext>().Use<ApplicationDbContext>(new ApplicationDbContext());

Categories

Resources