XUnit Test DbContext - c#

How can I pass the DBContext when initializing in CustomWebApplicationFactory to make tests in the xUnit file or correct the error ?
public class CustomWebApplicationFactory : WebApplicationFactory<quest_web.Startup>
{
public APIDbContext Context;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(ConfigureServices)
.UseEnvironment("Development");
}
private void ConfigureServices(WebHostBuilderContext webHostBuilderContext, IServiceCollection serviceCollection)
{
var dbContextService = serviceCollection.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<APIDbContext>));
if(dbContextService != null)
{
serviceCollection.Remove(dbContextService);
}
var provider = serviceCollection
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
serviceCollection.AddDbContext<APIDbContext>(contextOptions => {
contextOptions.UseInMemoryDatabase("quest_web");
contextOptions.UseInternalServiceProvider(provider);
});
var sp = serviceCollection.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
Context = scopedServices.GetRequiredService<APIDbContext>();
Context.Database.EnsureCreated();
Context.Database.EnsureDeleted();
}
}
}
When calling the test the context returns the error
public class Test
{
private readonly APIDbContext _context;
private readonly HttpClient _client;
public Test()
{
factory = new CustomWebApplicationFactory();
_client = factory.CreateClient();
_context = factory.Context;
}
[Fact]
private async void Test_dbcontext()
{
User user = new User("username", "password");
_context.User.Add(user);
_context.SaveChanges();
}
}
There is my feeback error
System.ObjectDisposedException : Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
[UPDATE]
Use Singleton and not scoped lifetime

Related

How to setup SQLite in WebApplicationFactory?

I am writing an integration test for my ASP.NET Core MVC application. The test is about to send a POST request to controller and then check if database was updated correctly.
I have a CustomWebApplicationFactory where I am trying to configure SQLite in-memory database, but probably I do something wrong.
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
private SqliteConnection Connection;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
Connection = new SqliteConnection("DataSource=:memory:");
Connection.Open();
builder.UseEnvironment("Development");
builder.ConfigureTestServices(services =>
{
// Unregister existing database service (SQL Server).
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<AppDbContext>));
if (descriptor != null) services.Remove(descriptor);
// Register new database service (SQLite In-Memory)
services.AddDbContext<AppDbContext>(options => options.UseSqlite(Connection));
});
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Connection.Close();
}
}
My test looks like this:
public class OrderControllerTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _httpClient;
private readonly AppDbContext _context;
public OrderControllerTests(CustomWebApplicationFactory<Startup> factory)
{
_httpClient = factory.CreateDefaultClient();
var scopeFactory = factory.Services.GetService<IServiceScopeFactory>();
using var scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<AppDbContext>();
}
[Fact]
public async Task Create_Post_OrderIsCreated()
{
// ...
_context.Customers.Add(customer);
_context.SaveChanges();
// ...
}
}
When I run the test, the line _context.Customers.Add(customer); triggers the CustomWebApplicationFactory.Dispose() method and I get an error:
System.ObjectDisposedException : Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'AppDbContext'.
The error message is very descriptive, but I don't know how to solve it. Why is the database context getting disposed?
I think I've figured it out, the issue is that _context is available only in the _scope, so I removed using and now I have shared database between class tests. Each test have also the option to empty and populate the database.
private readonly HttpClient _httpClient;
private readonly AppDbContext _context;
private readonly IServiceScope _scope;
public OrderControllerTests(CustomWebApplicationFactory<Startup> factory)
{
_httpClient = factory.CreateDefaultClient();
_scope = (factory.Services.GetRequiredService<IServiceScopeFactory>()).CreateScope();
_context = _scope.ServiceProvider.GetRequiredService<AppDbContext>();
// database is now shared across tests
_context.Database.EnsureCreated();
}
#Muflix I couldn't add to comment after your comment question as too long so here it is instead...
I'm using xUnit and Shouldly:
Rather than create the scoped service in the constructor and then access from the test(s) I have been injecting the factory (stored as private readonly CustomWebApplicationFactory<Startup> _factory;) then in the tests I use the factory to create a scope and access a scoped service (such as dbcontext) from within each test. The tests share the database due to the fact that the WebApplicationFactory is a class fixture or collection fixture and this maintains a single database connection through its use of the DatabaseFixture member (note that this is not strictly being used as a fixture here it is simply instantiated as a member of the web application factory, it is so called because it is used elsewhere in my code as a unit testing fixture). The constructor in the WebApplicationFactory correctly instantiates the DatabaseFixture class which in turn opens the database connection and only closes it when the WebApplicationFactory (and therefore the DatabaseFixture) is disposed.
My Test:
public class MyControllerTests : IClassFixture<MySQLAppFactory<Startup>> // see below for MySQLAppFactory
{
private readonly MySQLAppFactory<Startup> _factory; // shared for ALL tests in this class (classFixture)
public MyControllerTests(MySQLAppFactory<Startup> factory)
{
_factory = factory;
}
[Theory] //example of many tests all of which use SAME _factory instance
[JsonArrayData("Path-to-Somewhere/MyData.json")] // pass some data
public async Task Post_should_do_whatever(MyRequest request) // I'm using Shouldly
{
var client = _factory.CreateClient(); // create a client form the single instance of webApplicationFactory
var httpContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"api/my", httpContent);
response.StatusCode.ShouldBe(HttpStatusCode.Whatever);
}
}
The MySQLAppFactory creates its own single DatabaseFixture (TestDatabase) during construction (it removes the existing database context from the services and replaces with a context that uses this DatabaseFixture). This same database class persists (the connection is held open) and it is used throughout integration tests. Each time a new context is created is is attached to the same database connection. Its the same base class as used for unit testing (but in the case of unit testing I mostly use a SQLite derived database, rather than MySQL).
public class MySQLAppFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
private readonly DatabaseFixture _databaseFixture; // THIS factory has THE single Database Fixture instance here
private bool _disposed;
public MySQLAppFactory()
{
_databaseFixture = new MySQLFixture(); // Create the single instance of MySQL fixture here
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder
.UseEnvironment("Testing")
.ConfigureServices(services => //also (as of ASP NET Core 3.0) runs after TStartup.ConfigureServices
{
// remove DbcontextOptions from original API project
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<MyContext>)); services.Remove(descriptor);
// add the test database context instead
services.AddScoped<MyContext>(_ => _databaseFixture.GetContext());
var sp = services.BuildServiceProvider();
});
}
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_databaseFixture?.Dispose();
}
}
base.Dispose(disposing);
_disposed = true;
}
~MySQLAppFactory() => Dispose(false);
}
The Fixture (base):
public abstract class DatabaseFixture : IDisposable
{
private readonly object _seedLock = new object();
private bool _disposed = false;
public DbConnection dbConnection { get; protected set; }
protected abstract bool IsInitialized(bool init = false);
protected abstract DbContextOptions<IMAppContext> GetBuildOptions();
// Note that DbContext instances created in this way should be disposed by the
// calling code (typically with a using {} block).
public MyTestContext GetContext(DbTransaction transaction = null)
{
var context = new MyTestContext(GetBuildOptions());
if (transaction != null)
{
context.Database.UseTransaction(transaction);
}
return context;
}
protected void Seed()
{
// lock and seed here
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// dispose managed state (managed objects) that implement IDisposable
dbConnection?.Dispose();
}
// free unmanaged resources (unmanaged objects) and override a finalizer below.
////if anything exists here then the finalizer is required
// set large fields to null to help release them faster.
_disposed = true;
}
}
}
The Fixture (MySQLDerived - as I also use others):
public sealed class MySQLFixture : DatabaseFixture
{
private bool _initialised = false;
private bool _disposed = false;
private readonly string _connectionString;
private readonly string _databaseName;
public MySQLFixture()
{
_databaseName = "some name possibly derived from config or guid etc";
_connectionString = "using _databasename and possibly config, build or environment etc";
dbConnection = new MySqlConnection(_connectionString);
Seed();
}
protected override bool IsInitialized(bool init = false)
{
if (!init)
{
return _initialised;
}
else
{
_initialised = init;
return _initialised;
}
}
protected override DbContextOptions<IMAppContext> GetBuildOptions()
{
return new DbContextOptionsBuilder<IMAppContext>().UseMySQL(dbConnection).Options;
}
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// dispose managed state (managed objects) that implement IDisposable
}
// free unmanaged resources (unmanaged objects) and override a finalizer below.
////if anything exists here then the this.finalizer is required below
dbConnection.Open();
var command = dbConnection.CreateCommand();
command.CommandText = $"DROP SCHEMA IF EXISTS `{_databaseName.ToLower()}`";
command.ExecuteNonQuery();
// set large fields to null to help release them faster.
}
base.Dispose(disposing);
_disposed = true;
}
~MySQLFixture() => Dispose(false);
}

Decorator for creating Scope with ScopedLifestyle.Flowing in Simple Injector

I need some help to understand what it's wrong in my configuration of the container.
I based this implementation by using this example.
Basically i need to implement some use case as database command based on that interface
public interface IDatabaseCommand<TResult, TParam>
{
TResult Execute(TParam commandParam);
}
and i want to use a decorator that add the transaction safe functionality.
Every command need to use a dedicated DbContext and the transaction has to be executed on that context
To do this i have implemented
Transactional Decorator:
public class TransactionDatabaseCommandDecorator
: IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
private readonly Container _container;
private readonly Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>
_decorateeFactory;
public TransactionDatabaseCommandDecorator(
Container container,
Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>> decorateeFactory)
{
_container = container;
_decorateeFactory = decorateeFactory;
}
public DatabaseResult Execute(BusinessCommandParams1 commandParam)
{
DatabaseResult res;
using (AsyncScopedLifestyle.BeginScope(_container))
{
var _command = _decorateeFactory.Invoke();
var factory = _container
.GetInstance<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>();
using (var transaction = factory.CreateDbContext(
new[] { "" }).Database.BeginTransaction())
{
try
{
res = _command.Execute(commandParam);
transaction.Commit();
}
catch (Exception e)
{
Console.WriteLine(e);
transaction.Rollback();
throw;
}
}
}
return res;
}
}
Example of implementation:
public class WpfRadDispenserUOW : IUnitOfWork<WpfRadDispenserDbContext>
{
private readonly IDesignTimeDbContextFactory<WpfRadDispenserDbContext> _factory;
private WpfRadDispenserDbContext _context;
private IDbContextTransaction _transaction;
public bool IsTransactionPresent => _transaction != null;
public WpfRadDispenserUOW(IDesignTimeDbContextFactory<WpfRadDispenserDbContext> fact)
{
_factory = fact ?? throw new ArgumentNullException(nameof(fact));
}
public WpfRadDispenserDbContext GetDbContext() =>
_context ?? (_context = _factory.CreateDbContext(null));
public IDbContextTransaction GetTransaction() =>
_transaction ?? (_transaction = GetDbContext().Database.BeginTransaction());
public void RollBack()
{
_transaction?.Rollback();
_transaction?.Dispose();
}
public void CreateTransaction(IsolationLevel isolationLevel) => GetTransaction();
public void Commit() => _transaction?.Commit();
public void Persist() => _context.SaveChanges();
public void Dispose()
{
_transaction?.Dispose();
_context?.Dispose();
}
}
Some command:
public class BusinessCommand1 : IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
private readonly IUnitOfWork<WpfRadDispenserDbContext> _context;
public BusinessCommand1(IUnitOfWork<WpfRadDispenserDbContext> context)
{
_context = context;
}
public DatabaseResult Execute(BusinessCommandParams1 commandParam)
{
//ToDo: use context
return new DatabaseResult();
}
}
Registration of container:
var container = new Container();
container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing;
container.Register<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>(() =>
{
var factory = new WpfRadDispenserDbContextFactory();
factory.ConnectionString =
"Server=.\\SqlExpress;Database=Test;Trusted_Connection=True";
return factory;
});
container.Register<IUnitOfWork<WpfRadDispenserDbContext>, WpfRadDispenserUOW>(
Lifestyle.Scoped);
container
.Register<IUnitOfWorkFactory<WpfRadDispenserDbContext>, WpfRadDispenserUOWFactory>();
//Command registration
container.Register<
IDatabaseCommand<DatabaseResult, BusinessCommandParams1>,
BusinessCommand1>();
//Command Decorator registration
container.RegisterDecorator(
typeof(IDatabaseCommand<DatabaseResult, BusinessCommandParams1>),
typeof(TransactionDatabaseCommandDecorator),Lifestyle.Singleton);
The problem is that when i try to execute
var transactionCommandHandler =
_container.GetInstance<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>();
usecase.Execute(new BusinessCommandParams1());
i receive correctly an instance of TransactionDatabaseCommandDecorator but when the i try to get the instance from the factory i receive this error
SimpleInjector.ActivationException: WpfRadDispenserUOW is registered using the 'Scoped' lifestyle, but the instance is requested outside the context of an active (Scoped) scope. Please see https://simpleinjector.org/scoped for more information about how apply lifestyles and manage scopes.
in SimpleInjector.Scope.GetScopelessInstance(ScopedRegistration registration)
in SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration registration, Scope scope)
in SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
in WpfRadDispenser.DataLayer.Decorator.TransactionDatabaseCommandDecorator.Execute(BusinessCommandParams1 commandParam) in C:\Work\Git\AlphaProject\WpfRadDispenser\WpfRadDispenser.DataLayer\Decorator\TransactionDatabaseCommandDecorator.cs: riga 29
in WpfRadDispenser.Program.Main() in C:\Work\Git\AlphaProject\WpfRadDispenser\WpfRadDispenser\Program.cs: riga 47
The problem here is that i want to use a dbcontext that it's created and controlled by his decorator.
But the constructor injection it's handled by container so how i can inject the context created by the decorator inside the command?
Basically i want to having something like that made by the decorator of the command
var context = ContextFactory.GetContext();
try
{
var transaction = context.database.GetTransaction();
var command = new Command(context);
var commandParams = new CommandParams();
var ret = command.Execute(commandParams);
if (!ret.Success)
{
transaction.Discard();
return;
}
transaction.Commit();
}
catch
{
transaction.Discard();
}
but made with DI and Simple Injector
Maybe there is some issue or several issue on my design but i'm new on DI and i want to understand better how the things works.
Just to recap i need to use a lot of command database in which every command has to have an isolated context and the functionality of transaction has to be controlled by an extra layer inside the decorator.
The problem is caused by the mixture of both flowing/closure scoping vs ambient scoping. Since you are writing a WPF application, you choose to use Simple Injector's Flowing scopes feature. This allows you to resolve instances directly from a scope (e.g. calling Scope.GetInstnace).
This, however, doesn't mix with Ambient Scoping, as is what AsyncScopedLifestyle.BeginScope does.
To fix this, you will have to change the implementation of your decorator to the following:
public class TransactionDatabaseCommandDecorator
: IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
private readonly Container _container;
private readonly Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>
_decorateeFactory;
public TransactionDatabaseCommandDecorator(
Container container,
Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>> decorateeFactory)
{
_container = container;
_decorateeFactory = decorateeFactory;
}
public DatabaseResult Execute(BusinessCommandParams1 commandParam)
{
DatabaseResult res;
using (Scope scope = new Scope(_container))
{
var command = _decorateeFactory.Invoke(scope);
var factory = scope
.GetInstance<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>();
...
}
return res;
}
}
Note the following about the decorator above:
It gets injected with a Func<Scope, T> factory. This factory will create the decoratee using the provided Scope.
The execute method now creates a new Scope using new Scope(Container) instead of relying on the ambient scoping of AsyncScopedLifestyle.
The Func<Scope, T> factory is provided with the created scope.
The IDesignTimeDbContextFactory<T> is resolved from the Scope instance, instead of using the Container.

Resolving DbContext on a Test project returns a DbContext with a wrong connectionString

I have a test project that uses IClassFixture of a generic factory class.
For example
public class WeatherForecastAcceptanceTest
: IClassFixture<WebApi1ApplicationFactory<Startup>>, IDisposable
{
.....
}
after the Startup class executes ConfigureServices and Configure methods it executes the ConfigureWebHost where I remove the original DbContext and add a new one that runs in memory.
public class WebApi1ApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
//remove the injected DbContext and inject in-memory
services.RemoveAll(typeof(DbContext));
var connection = new SqliteConnection("Data Source=:memory:");
services.AddDbContext<WebApi1DbContext>(
options => options.UseSqlite(connection));
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
//ERROR HERE WITH THE RESOLVED DbContext
using (var dbContext = scope.ServiceProvider
.GetRequiredService<WebApi1DbContext>())
{
try
{
dbContext.Database.OpenConnection();
dbContext.Database.EnsureCreated();
}
catch (Exception ex)
{
throw;
}
}
}
});
}
}
The DbContext that I am resolving has the original connectionString instead of InMemory, as a result, all my testings are inserting content on my original database.
Here is how I am using my WebApplicationFactory
public class WeatherForecastAcceptanceTest : IClassFixture<WebApi1ApplicationFactory<Startup>>, IDisposable
{
private WebApi1ApplicationFactory<Startup> _factory;
private HttpClient _client;
public WeatherForecastAcceptanceTest(WebApi1ApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async Task GetAll_ReturnsElements_WhenPostWasExecutedSucessfully()
{
// Arrange
var weatherForecastForCreationDto = CreateRandomWeatherForecastForCreationDto();
var content = new JsonContent(weatherForecastForCreationDto);
//setting the content-type header
content.Headers.ContentType = new MediaTypeWithQualityHeaderValue(HttpMediaTypes.WeatherForecastType1);
//create an object
using (var client = _factory.CreateClient())
{
//act
var responsePost = await _client.PostAsync(ApiRoutes.WeatherForecast.CreateWeatherForecast, content);
//assert
responsePost.StatusCode.Should().Be(StatusCodes.Status201Created);
}
//check that the object was inserted
using (var client = _factory.CreateClient())
{
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(HttpMediaTypes.WeatherForecastType1));
// Act
var response = await _client.GetAsync(ApiRoutes.WeatherForecast.GetWeatherForecast);
// Assert
response.StatusCode.Should().Be(StatusCodes.Status200OK);
var returnedGet = await response.Content.ReadAsAsync<WeatherForecastDto[]>();
returnedGet.Should().Contain(dto => dto.Summary == weatherForecastForCreationDto.Summary);
}
}
public void Dispose()
{
_client?.Dispose();
_factory?.Dispose();
}
}
How can I resolve the in-memory DbContext that was injected?
After dealing with this problem for a while, based on the documentation this is what they are suggesting
On your startup read a test environment variable, like you are doing with production or development, and inject the correct DbContext for that.
For example to inject an on memory DBContext with Sqlite.
if (_env.IsEnvironment("Test"))
{
var connection = new SqliteConnection(connectionDb);
services.AddDbContextPool<WebApi1DbContext>(options => options.UseSqlite(connection));
}
Instead of trying to remove the DbContext in that way, the idea is try to set the environment variable to the WebApplicationFactory because the AddDbContext sets more dependencies on the container that RemoveAll is not considering.
public class WebApi1ApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Test");
builder.ConfigureServices(services =>
{
//DbContext in-memory is injected taking in consideration the Test environment.
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
using (var dbContext = scope.ServiceProvider.GetRequiredService<WebApi1DbContext>())
{
try
{
dbContext.Database.OpenConnection();
dbContext.Database.EnsureCreated();
}
catch (Exception ex)
{
//Log errors or do anything you think it's needed
throw;
}
}
}
});
}
}
This action will allow you to use the DbContext to retrieve the DbContext you want for the test environment.

How to create separate in memory database for each test?

I'm writing a test with xunit on .NET Core 3.0 and I have a problem with the in-memory database. I need a separate database for each test but now I create a single database that causes problems, but I have no idea how to create a new database for each test.
public class AccountAdminTest : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
}
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
});
}
}
Now it's look like this but still dosen't work. When i change lifetime on AddDbContext it doesn't change anything.
public class AccountAdminTest : IDisposable
{
public AccountAdminTest(ITestOutputHelper output)
{
this.output = output;
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = _scope.ServiceProvider.GetService<ApplicationDbContext>();
var _user = User.getAppAdmin();
_context.Add(_user);
_context.SaveChanges(); //Here i got error on secound test. It says "An item with the same key has already been added"
}
public void Dispose()
{
_scope.Dispose();
_factory.Dispose();
_context.Dispose();
_client.Dispose();
}
I can't get token when use Guid as db name. It says that username/password is not valid. I use IdentityServer for authentication
public async Task<string> GetAccessToken(string userName, string password, string clientId, string scope)
{
var disco = await _client.GetDiscoveryDocumentAsync("https://localhost:44444");
if (!String.IsNullOrEmpty(disco.Error))
{
throw new Exception(disco.Error);
}
var response = await _client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientId,
Scope = scope,
UserName = userName,
Password = password,
});
return response.AccessToken;
}
All you need to change is this code here:
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
Instead of the constant value "IdentityDatabase", use something like Guid.NewGuid().ToString():
context.UseInMemoryDatabase(Guid.NewGuid().ToString());
Then, every time the context is fetched, it will be using a new in-memory database.
Edit: You must specify an unique name for every test run to avoid sharing the same InMemory database as slightly mentioned here and already answered here and here.
Nonetheless the suggestion below to switch to "Constructor and Dispose" still applies.
IClassFixture is not the right tool to use, the documentation says:
When to use: when you want to create a single test context and share it among all the tests in the class, and have it cleaned up after all the tests in the class have finished.
In your case you have to use "Constructor and Dispose", the documentation says:
When to use: when you want a clean test context for every test (sharing the setup and cleanup code, without sharing the object instance).
So your test will be:
public class AccountAdminTest
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
//
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
//Tests...
public void Dispose() {
_scope.Dispose();
_factory.Dispose();
//Dispose and cleanup anything else...
}
}
Alternatively you can specify a lifetime of ServiceLifetime.Transient for your DbContext using this overload of .AddDbContext

SignalR - Multitenant Dependency Injection

I need to resolve a DbContext based on tenant's owin value. But in the pipeline of method OnDisconnected of hub, the HttpContext is not accessible.
My hub class:
public class UserTrackingHub : Hub
{
private readonly UserContext _context;
public UserTrackingHub(UserContext context) { ... }
public override async Task OnConnected() { /* OK HERE...*/ }
public override async Task OnDisconnected(bool stopCalled)
{
// NEVER FIRES WITH IF I USE THE CTOR INJECTION.
var connection = await _context.Connections.FindAsync(Context.ConnectionId);
if (connection != null)
{
_context.Connections.Remove(connection);
await _context.SaveChangesAsync();
}
}
}
Here's my Autofac config:
public static IContainer Register(IAppBuilder app)
{
var builder = new ContainerBuilder();
// Other registers...
builder.Register<UserContext>(c =>
{
// Details and conditions omitted for brevity.
var context = HttpContext.Current; // NULL in OnDisconnected pipeline.
var owinContext = context.GetOwinContext();
var tenant = owinContext.Environment["app.tenant"].ToString();
var connection = GetConnectionString(tenant);
return new UserContext(connection);
});
var container = builder.Build();
var config = new HubConfiguration
{
Resolver = new AutofacDependencyResolver(container)
};
app.MapSignalR(config);
return container;
}
Can someone help me to identify the tenant OnDisconnected in this or any other way?
Thanks!
For anyone interested, I end up injecting a context factory instead the context itself:
public class UserTrackingHub : Hub
{
private readonly Func<string, UserContext> _contextFactory;
public UserTrackingHub(Func<string, UserContext> contextFactory) { ... }
public override async Task OnConnected() { ... }
public override async Task OnDisconnected(bool stopCalled)
{
var tenant = Context.Request.Cookies["app.tenant"].Value;
using (var context = _contextFactory.Invoke(tenant))
{
var connection = await context.Connections.FindAsync(Context.ConnectionId);
if (connection != null)
{
context.Connections.Remove(connection);
await context.SaveChangesAsync();
}
}
}
}
And Autofac's config:
// Resolve context based on tenant
builder.Register<Func<string, UserContext>>(c => new Func<string, UserContext>(tenant => UserContextResolver.Resolve(tenant)));

Categories

Resources