4

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;
        }

2 Answers 2

6

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.

Sign up to request clarification or add additional context in comments.

1 Comment

This does not work for me using Hotchocolate's IRequestExecutor. I do not know why. It seems like after executing a Request the DB is empty.
2

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

7 Comments

It didn't help. I still have the same problem.
What have you tried exactly ? Can you provide more information about your problem ? Keep in mind that InMemoryDatabase does not function like a relational database
I tried "Constructor and Dispose" and changed lifetime and it didn't change anything. I run tests and first one pass but in the next one i have records in database from the previous test.
Ofcours, i did it.
What happens if you use a different database name for each test like: context.UseInMemoryDatabase(Guid.NewGuid().ToString()); ?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.