DiPix
DiPix

Reputation: 6083

Is this the correct way to get context in xunit tests .NET Core?

In my test I have to have access to database context (real database, it's not in memory). Because after post I want to ensure that record has been saved in database.

This is what I have, it works perfectly but I don't have enough experiance to be sure that this is correct approach for getting context.

public class ValuesControllerTests : TestHostFixture
{
    [Theory]
    [InlineData("/values/sample")]
    public async Task Sample_WhenCreatingSampleData_ThenIsAddedToDatabase(string url)
    {
        // given
        var command = new AddSampleCommand { Name = "TestRecord" };

        // when
        var httpResponse = await Client.PostAsJsonAsync(url, command);

        // then
        httpResponse.EnsureSuccessStatusCode();

        using (var dbContext = GetContext())
        {
            dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord").ShouldNotBeNull();
        }
    }
}


public abstract class TestHostFixture : IClassFixture<CustomWebApplicationFactory<Startup>>
{
    protected readonly HttpClient Client;
    private readonly CustomWebApplicationFactory<Startup> _factory;

    protected TestHostFixture()
    {
        _factory = new CustomWebApplicationFactory<Startup>();
        Client = _factory.CreateClient();
    }

    protected MyContext GetContext()
    {
        return _factory.Server.Host.Services.CreateScope().ServiceProvider.GetService<MyContext>();
    }
}

So just to sum up - in test I'm getting Context by:

using (var dbContext = GetContext())
{
    dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord").ShouldNotBeNull();
}

And GetContext method:

protected MyContext GetContext()
{
    return _factory.Server.Host.Services.CreateScope().ServiceProvider.GetService<MyContext>();
}

Please let me know if this is fine, or maybe I should refactor it somehow because of some potential issue in future.

Upvotes: 0

Views: 4003

Answers (2)

Nkosi
Nkosi

Reputation: 247323

Improving on the implementation provided by poke, you could consider creating a delegate to handle the proper disposal of the created scope.

For example

protected void GetContext(Action<MyContext> test) {
    using(var scope = _factory.Server.Host.Services.CreateScope()) {
        var context = scope.ServiceProvider.GetRequiredService<MyContext>();
        test(context);
    }
}

When exercising your test simple call the delegate

[Theory]
[InlineData("/values/sample")]
public async Task Sample_WhenCreatingSampleData_ThenIsAddedToDatabase(string url) {
    // given
    var command = new AddSampleCommand { Name = "TestRecord" };

    // when
    var httpResponse = await Client.PostAsJsonAsync(url, command);

    // then
    httpResponse.EnsureSuccessStatusCode();

    GetContext(dbContext => {
        var item = dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord");
        item.ShouldNotBeNull();
    });
}

Upvotes: 0

poke
poke

Reputation: 388003

Generally, this is fine: You definitely should make use of the host’s DI container to retrieve the database context. Since the database context is scoped, it is also correct to create a new service scope to retrieve the context.

However, since the DI container is what usually manages the lifetime of the objects it creates, you should leave the disposal of the database context up to the DI container, and instead dispose of the service scope.

While it will probably not matter much in a unit test, which will be cleaned up quickly anyway (and since you are disposing the context you also won’t leak database connections), it’s still a better style and safer in the long run.

So dispose the service scope instead:

// …
httpResponse.EnsureSuccessStatusCode();

using (var scope = Host.Services.CreateScope())
{
    var dbContext = scope.ServiceProvider.GetService<MyContext>();
    var item = dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord");
    item.ShouldNotBeNull();
}

Upvotes: 1

Related Questions