ASP.NET Core 5 & EntityFramework Core: Clean, clear and fluent integration tests with Calzolari.TestServer.EntityFramework, FluentAssertion.Web and xUnit
Introduction
Not long ago I was asked to do integration tests with ASP.NET Core and Entity Framework Core and I was confronted with some difficulties, in particular the fact of testing Entity Framework Core in integration? I have not found a simple tutorial to create clear and simple tests and especially how to get around the limitations of Sql in memory when using EntityFramework Core. So I had to find work arounds with SqlLite to do my tests while making sure that there are no data collisions in each test. In addition, I wanted to make sure that I was using AAA (Arrange, Act, Assert) in such a way as to maintain optimal ideal readability. So I had the idea to create the Calzolari.TestServer.EntityFramework library which encapsulates all the management of EntityFramework Core in integration, this library also uses Flurl for writing Http clients in a fluid way and FakeBearerToken allowing to pass tokens in the simplest way in endpoints protected by JWTs. In this article I will also show how to use Calzolari.TestServer.EntityFramework in conjunction with FluentAssertions.Web which is an assertion library for the web and xUnit in order to perform really clean integration tests! I used as well AutoFixture to facilitate somme Arrange.
Note that Parallelism MUST BE deactivated while testing, the database is created and removed after each test which avoids any data collision, the DbContext is reinstancianted between each test. It’s not recommended for large databases.
Test scenario
We’ll consider the following scenario, an endpoint protected by a JWT which gets a country by its Id, and the second one protected with a JWT that must contains the Admin role which creates a country:
Setup your integration test project
Install first the following packages, for example in command line in the Package Manager console:
Install-Package Calzolari.TestServer.EntityFramework Install-Package AutoFixture Install-Package FluentAssertions.Web Install-Package xunit Install-Package xunit.runner.visualstudio Install-Package Microsoft.AspNetCore.Mvc.Testing Install-Package Microsoft.NET.Test.Sdk
Don’t forget to import as reference the ASP.NET Core project you want to test.
Create a dedicated Startup.cs file for your integration tests
I strongly suggest to create specific Startup.cs file (name it TestStartup.cs for example) for better clarity, don’t use the Startup.cs file from the web project you want to test.
Then :
- Add the method AddApplicationPart which takes in parameter any class of your webapplication (for example the Startup class), this is needed to retrieve controllers and register them in the integration test project
- Register the DbContext with the following method: AddIntegrationTestDbContext, this is needed for integration test to be working with EF Core, behind the scene it register your DbContext with SQLite Database
- Register the fake bearer token authentication with the AddFakeBearerToken method, this is needed to create an identity with a fake token while performing integration tests
It should look like this:
Create a Web factory to create the test server
Inherit from FlurlWebFactory, generic T can be any class of your webapplication (for example the Startup class, the reason why is explanied in a previous post here: TestServer & ASP.NET Core 5: Fix “System.InvalidOperationException : Solution root could not be located using application root” with a custom Startup file – Anthony Giretti’s .NET blog), then override CreateHostBuilder and use your TestStartup.cs file like this:
Create a xUnit collection definition
A collection definition allows injection dependency (Singleton lifetime) within your test classes. The following example shows how to register a collection named “AssemblyFixture” that allows to pass into your test classes the TestFactory we defined just before:
Create a base test class
Inherit from IntegrationTestBase<TDbContext, T>, TDbContext is your DbContext and T is the same class that you defined as generic parameter on FlurlWebFactory when you wrote your web factory, then, add [Collection(“AssemblyFixture”)] attribute on your base test class. This is here where you can add AutoFixture or anything else that you need for your all your tests. Your base test class should look like this:
Write your tests
Create a test class and inherit from the base test class. Pass the web factory by injection dependency and write your tests. I suggest to use FluentAssertions for your assertions
To feed the database use the method Arrange like this
var countries = Fixture.Build<Country>()
.CreateMany(3);
Arrange(dbContext =>
{
dbContext.Countries.AddRange(countries);
});
If you expect an auto incremented Id, it’s filled automatically like this:
var country = Fixture.Create<Country>();
Arrange(dbContext => { dbContext.Countries.Add(country); });
// country.CountryId CountryId is filled
Create a fake token to be authenticated while calling the endpoint to test, sub is the claim that represents the identity within a JWT:
var token = new
{
sub = "Anthony Giretti"
};
Then call your System Under Test (SUT):
var response = await BASE_REQUEST.Route(BaseRoute).FakeToken(token).GetAsync();
And assert the result by using FluentAssertions.Web:
response.ResponseMessage
.Should()
.Be200Ok()
.And
.BeAs(countries);
The response is typed as IFlurlResponseMessage, whichs exposes the native HTTP ResponseMessage , that’s why I had to write response.ResponseMessage to get the HTTP response.
The test class should look like this:
You can find more example here, especially with Authorization based on Roles on a POST endpoint:
Conclusion
This post aimed to show how you could test your ASP.NET Core application which uses EntityFramework Core, as you can see, all the complexity brought by EntityFramework Core in integration testing has been encapsulated. Don’t forget it’s not recommanded for larage database, on my end, I’m testing my microservices like this.
I hope you liked this post, and if you have any suggestion, for example, adding new features in Calzolari.TestServer.EntityFramework, feel free to send me an email :).