Continuing the series of posts to explore different testing aspects in asp .net core. We’re going experiment with Test Server to create integration tests.
Test Server is a new features that asp .net core is bringing. It creates a browser abstraction, allowing us to simulate application’s behavior without opening a browser.
There are many ways to deal with the database in integration tests. The idea is to not affect the data that’s already in the database. We could use an entirely different database, creating and deleting it every time we run our tests.
Other approach is to use the same database as the application, but clean up data after each test. For example, if we add a register into the database and in the end of the test we remove it.
We can follow a transactional approach. Which means that we create a transaction scope within each unit test to not commit changes when we call
.SaveChanges(). Then, when the test is over, this transaction is disposed and no changes are committed to the database.
Entity Framework Core In-Memory database could also be used. Following this approach we won’t have to worry about storing data. But, how can we call this an integration test if we aren’t using a real database? Yeah, that’s right, this approach will fall into the unit test category.
For this post, we’re going to use the transactional approach.
I’m going to use the same project from my last post. It’s a simple todo list app that I use to experiment with the new features from ASP.NET core.
In this post, I’m not going to show every implementation detail, feel free to download/clone the repository.
We’re going to create integration tests for the
ToDoListController class is boilerplate code, generated when creating a class via scaffolding. I made some tweaks, for instance, took out to
IToDoListService all code all database code related and there is some code related to an alert system. You can check about this alert system here.
The database and transactions configuration requires a more steps.
Let’s start with a class that ensures database exists and can run the code migrations. We call this class Fixture:
This class has a static constructor, running each time Fixture is instantiated. In the constructor we get the configuration which has the database connection string.
GetConfiguration() is responsible to get all configuration from
appsettings.json file. This file is pretty similar to the file we have on the web project, but we only have the connection string there:
The connection string is the same as in the web application, but feel free to change to whatever connection string you want.
CreateDatabase() method, we create a
DbContextOptionsBuilder object with
Configuration["DbConnection"] to get the connection string from the file. Then, we create a new
ToDoDbContext instance, sending the options into the constructor. Finally, we call
.Migrate() to run all code migrations. This method also creates the database if needed.
Done with the Fixture class, we can move on to the next step.
Now, we need a class which will contain the transaction scope configuration and instantiate Test Server. Also, it will inherit Fixture class, to guarantee that the database exists. Without further ado, let’s take a look at the
WebFixture<TStartup> is a generic class which accepts an
Startup.cs object. If you take look on the constructor,
TestServer object receives an
IWebHostBuilder object in its constructors parameter. The Startup contains all container and HTTP request pipeline configurations.
WebHost.CreateDefaultBuilder()method to create
TestServer constructor receives our builder as a parameter.
TestServer instance, we create the client and assign it to a public variable.
Client is responsible to make calls to controller actions. Next, we get the registered services from
server.Host and assign it to a private variable
GetService() method to get
DbContext object and assign it to a public variable, so it will be accessible in classes that inherits it. To finish the constructor, we open a transaction with
DbContext.Database.BeginTransaction() and store it in a public variable. It opens a transaction in the database then, the database will not commit the changes right away, it only when we call
This class implements
IDisposable interface. The idea is to rollback all database changes and dispose the transaction after each test runs.
About the startup stub class that we need, let’s take a look on it in the following section.
This class is similar to Startup class from the web project, but there’s one catch that will make the difference when making integration tests with transactions.
The differences is when calling
WebHost.CreateDefaultBuilder().UseStartup<TStartup>(); on the
WebFixture<TStartup> class constructor, we need a class containing both
Configure(...) methods. I know that isn’t good to have duplication, but let me explain why. The difference is on how we register the
AddDbContext<T> has an overload with three parameters, one for the option and two others responsible to control the
DbContextOptions life cycles. I’m setting both object to be Singletons, you may think that singletons are evil but there is a reason.
The default life cycle for both objects is Scoped, meaning that for each request the application creates a new instance of them. Scoped life cycle would not work, because
WebFixture<TStartup> class would get an instance of the
DbContext and the test would get another instance. So, the changes we would make on the
DbContext from the
WebFixture<TStartup> class are not going reflect when calling the application on the test. That is why we need
DbContext to be a singleton, it’s gonna be only one
DbContext instance throughout the whole application. Then, when we make a change, the client’s calls will be up to date since it is the same
Unit Test Class
I’m using three test libraries on this project. The first one is xUnit, it’s the unit test framework. FluentAssertions, which helps us to create better assertions for our test. And AutoFixture framework, which allow us to create objects with dummy data and focus only on what’s relevant to test. AutoFixture is a powerful framework, I encourage you to check it want to learn more about it.
We have to create a class to implement the tests, since we’re going to test
ToDoListController class, I’m going to call it
The class inherits from
IClassFixture<WebFixture<StartupStub>> using the
StartupStub class created before.
IClassFixture<T> acts as
[ClassInitialize] from MsTest framework or
[OneTimeSetup] from NUnit. Meaning that whatever object we place, its constructor will only run one time before all tests from the class run.
ToDoListControllerTests constructor receives
IClassFixture injects and we call it
_fixture will gives access to all public properties from
Transaction. These are the properties we need to create our integration tests.
Let’s move to the first test.
First Integration Test
For the first test, let’s start with a simple one. It’s a test to check if the client is able to call the application correctly returning a http success status code:
We don’t need any setup here, just have to call the application root. To check if it was successful, we call
EnsureSuccessStatusCode() on the response.
As can be seen, we send a http get request to the application root,
“/”. This is the equivalent of send a request to
“/ToDoList/Index”. Because the application default route is configured in this way on the
This is a fairly simple test, let’s move to a more complex test.
Index Action Response Contains Recently Added To Do List
In this test we’re going to check if a recently added to do list is present on the view.
In the arrange, we create a to do list and save it into the database. But, how we can see if the to do list is on the screen?
For that, we call the Index action (“/”) from the
ToDoListController, action responsible to show all to do lists from database. We aren’t able to cast the response object to
List<ToDoList> object because the response only contains HTML that the application will show on the screen.
One way to check if the response is right, is to get the response content with
.ReadAsStringAsync() to access its HTML. Finally, we check the HTML, looking for the to do list name that were created in the beginning of the test.
One more thing to talk about this test, in the arrange section, you can see that I’m using a factory to create a new
ToDoList object. It helps object creation and let the test cleaner:
ToDoListFactory class, we’re going to use AutoFixture framework to help us create dummy objects.
Create(...) method has a parameter to indicate how many
ToDoList objects it will create. I’m not going into details regarding AutoFixture, it’s not my objective here, but you can check more about it here.
Adding a new To Do List with a Post Request
To add a new to do list in our database, we have to call the http post version of the ToDoList/Create action. Since, we need to send complex data in the request, not only return data in the response:
To start, we create
formData, which consists of data we want to send to server. It’s a dictionary that will contain the model property name as a key and a value. The dictionary key has to be the same as the property in the object that the action is receiving.
Then, to post data into the server we have to call
.PostAsync() method in our client. This method has two parameters, one for the request uri and another one for the http content. For the second parameter, we’re creating a
FormUrlEncodedContent object with the
Now, moving to the test’s assertion. I’m just checking if the application redirects to its root, meaning the to do list creation was successful.
We run the test and we’re getting an error:
The error says that our request was a bad request. But why? It’s because we are missing one important aspect when working with asp.net core mvc and posting data to the server, the Anti Forgery Token.
Anti Forgery Token
Anti Forgery Token is responsible for prevent cross-site request forgery (CSRF) attack. It’s a method that generates a code and put it on the view to avoid send malicious or fake data to the server.
The method inserts an hidden HTML on the view:
In ASP.NET Core MVC the tag
<form> creates the Anti Forgery Token automatically when on a view.
So, the problem we’re having with this test is that the missing token because of the
[ValidateAntiForgeryToken] attribute on the
[HttpPost] create action. This attribute validates if the data on the request has the correct token. The token has to be in the request, but how can we do this?
To get the token, we have to make a request to a page that has a
<form>, get the code that
<form> generates and add it to our request:
The main focus of the
AntiForgeryHelper.cs class is the
It’s a static async method with that returns the anti forgery token value. Receives our client as a parameter to make a request to the
"/ToDoList/Create", which is a view that has the token. After that, we try to get the anti forgery cookie from the response using
TryGetAntiForgeryCookie(). If the cookie is nowhere to be found, we’re trying to get the cookie from a view that doesn’t have the <form> tag.
With the cookie in hands, we add it the client default request headers.
Finally, we can get the anti forgery token from the html in the response. To do this, we have to read the response content and scrap the token using a regex.
In the method’s beginning, there’s a check to see if the token already exists. I’m doing this because we don’t need to get a new token for each request, if the token is already exists, there’s no need to get new one.
Now, we need to add the token to our test and see if it’s working.
Add Anti Forgery Token To The Request
We need to to add the token into the
By adding the token in the request and getting the token using
AntiForgeryHelper.EnsureAntiForgeryTokenAsync() method, the test now works.
Edit Action Is Loading The Correct To Do List
In this test we want to load a to do list in the edit view and check if the view is loading it correctly.
We start by adding a new to do list into the database using
DbContext. Then, we call
GetAsync(), sending the url as parameter with the
toDoList.Id in the query string.
In the assertion, we read the response content and check if it contains the
toDoList.Id, meaning that application loaded the to do list in the view.
Calling Edit Action With a Null ToDoList Id
In this test, we want to check if the application returns a http not found status code when we make a call to
"/ToDoList/Edit/" without the to do list id on the query string:
Editing Previously Created ToDoList
Let’s say we want to edit a to do list:
First, we create a to do list and save it to the database. To finish the arrange, we create the
formData with the validation token and the data we want to edit.
I’m adding both “id” and “Id” to the form data.
"/ToDoList/Edit/" action needs the “id” on the query string and “Id” is for to do list object itself when receiving a call.
Then, we make a
PostAsync()call to the edit action.
Finally, we validate if the application redirects the user to the root(“ToDoList/Index”), meaning that the to do list was updated.
Posting An Invalid Model To Edit Action
How about about testing a negative flow?
We could post a model with an invalid state to check if the view shows the right errors messages. To get a model into an invalid state, we need to post the model without data in properties that have
[Required] data attribute.
To simulate this behavior, we can send data to the edit action(“ToDoList/Edit”) without
“Name” key on the
The action should return the response with the validation message in the its HTML. The validation message,
"The Name field is required.", is the default message when data attribute
[Required] is present on a property’s class.
Removing A ToDoList
For the last test, let’s just remove a to do list from the database using
The test’s structure is similar to the other ones. We’re sending only the to do list id in the request, because this is the only information necessary to remove the to do list from database.
In the assertion we check if the application redirect the user to index action, meaning the operation was successful.
References and Further Reading
- Leveling Up Your .Net Testing Patterns – Part II Transactional Integration Testing – nance.io
- Integration Testing for ASP.NET Core Applications – dotnetcurry
- Integration testing your Asp .Net Core App – Dealing with Anti Request Forgery (CSRF), Form Data and Cookies – Stefan Hendriks’ blog
- Easier functional and integration testing of ASP.NET Core applications – Scott Hanselman
- Integration tests in ASP.NET Core – Microsoft Docs