Reputation: 6083
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
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
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