Enabling Entity Framework Core In Memory Database
In Part 1 I setup my application to work with Specflow. However there is a dependency on my database. If I was to run the tests again they could fail as the same data keeps getting added. Therefore it is common to teardown and restore a database. This is time consuming and often manual. We could automate this on our Build Pipeline but there could be issues with accessing the database. Another issue is because it’s time consuming they aren’t being run by users locally or they are sometimes disabled on the Build pipeline.
That is where EF Core In-Memory Database Provider can help. This provides a basic representation of a database (Advance features in SQL Server aren’t available for example Temporal Tables) which is quick and easy to test against.
Let’s look at how to use this in our tests without re-writing out services.
Firstly let’s add the In Memory Database to our Test Project
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.7" />
Code language: HTML, XML (xml)
Then all we do is revisit the code in our Test Hook that Builds out Host and add one line
[BeforeTestRun(Order = 1)]
public static void BeforeTestRun()
{
var hostBuilder = Host.CreateDefaultBuilder()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseTestServer();
webBuilder.ConfigureTestServices(services => services.AddDbContext<MyContext>(opt => opt.UseInMemoryDatabase("Test")));
webBuilder.UseStartup<Startup>();
});
var host = hostBuilder.Start();
_client = host.GetTestClient();
}
Code language: C# (cs)
Even though our original code in the Startup specifies creating the context using SQL Server, our test services will take priority.
And that is it. No need write code to create and teardown the database and the performance is excellent, encouraging you to run these tests often.
Prepping the Database for the Test
If you are not using an Anemic Data model (i.e. you have DTOs and Entity Models and there is a mapping process) or maybe you are not exposing any creation or updating end points in your api, then you may need to add data to your test database for testing.
To do this we need to get hold of the database context in our SUT and populate it before querying the end point.
To start off let’s create a simple interface that encapsulate the database logic. This allows us to replace the implementation if we need to move onto SQLLite or a custom Dacpac deployment.
public interface IDatabase : IDisposable
{
void Add<T>(List<T> entities);
}
Code language: C# (cs)
Then we can extend the ISystemUnderTest to have this as a property
public interface ISystemUnderTest
{
Task PostAsync<T>(string route, T entity);
HttpResponseMessage Response { get; }
Task PutAsync<T>(string route, T entity);
Task GetAsync(string route);
Task DeleteAsync(string v);
IDatabase Database { get; }
}
Code language: C# (cs)
Then create an implementation for our IDatabase that takes our DatabaseContext. For example
public class EntityFrameworkDatabase : IDatabase
{
private readonly MyContext _dbContext;
public EntityFrameworkDatabase(MyContext dbContext)
{
_dbContext = dbContext;
}
public void Add<T>(List<T> entities)
{
foreach (var item in entities)
{
_dbContext.ChangeTracker.TrackGraph(item, e => {
if (e.Entry.IsKeySet)
{
e.Entry.State = EntityState.Unchanged;
}
else
{
e.Entry.State = EntityState.Added;
}
});
}
_dbContext.SaveChanges();
}
public void Dispose()
{
_dbContext.Dispose();
}
}
Code language: C# (cs)
Now we need to revisit our Hook to initialize this. I use the IServiceScope and get the DatabaseContext in BeforeScenatio and Dispose of it in AfterScenario.
I have moved the TestClient reference to the scope of the class as I need it to be able get the context created by the service.
private IServiceScope scope;
[BeforeScenario]
public void BeforeScenario()
{
_context.Client = _client;
scope = _host.Services.CreateScope();
var dbContext = (MyContext)scope.ServiceProvider
.GetService(typeof(MyContext));
_context.Database = new EntityFrameworkDatabase(dbContext);
}
[AfterScenario]
public void AfterScenario()
{
scope.Dispose();
}
Code language: C# (cs)
In my test scenario I can now load data directly in the database via the SUT and test my query. I have found this particularly useful when the database is a start schema and I need to serve up objects that may have been spread across multiple rows in the database. This tests my logic and I can ensure nothing breaks in the future.