Testing ASP.NET Core MVC web apps in-memory

This post was written and submitted by Javier Calvarro Nelson, a developer on the ASP.NET Core MVC team

Testing is an important part of the development process of any app. In this blog post we’re going to explore how we can test ASP.NET Core MVC app using an in-memory server. This approach has several advantages:

  • It’s very fast because it does not start a real server
  • It’s reliable because there is no need to reserve ports or clean up resources after it runs
  • It’s easier than other ways of testing your application, such as using an external test driver
  • It allows testing of traits in your application that are hard to unit test, like ensuring your authorization rules are correct

The main shortcoming of this approach is that it’s not well suited to test applications that heavily rely on JavaScript. That said, if you’re writing a traditional web app or an API then all the benefits mentioned above apply.

For testing MVC app we’re going to use TestServer. TestServer is an in-memory implementation of a server for ASP.NET Core app akin to Kestrel or HTTP.sys.

Creating and setting up the projects

Start by creating an MVC app using the following command:

dotnet new mvc -au Individual -uld --use-launch-settings -o .\TestingMVC\src\TestingMVC

Create a test project with the following command:

dotnet new xunit -o .\TestingMVC\test\TestingMVC.Tests

Next create a solution, add the projects to the solution and add a reference to the app project from the test project:

dotnet new sln
dotnet sln add .\src\TestingMVC\TestingMVC.csproj
dotnet sln add .\test\TestingMVC.Tests\TestingMVC.Tests.csproj
dotnet add .\test\TestingMVC.Tests\TestingMVC.Tests.csproj reference .\src\TestingMVC\TestingMVC.csproj

Add references to the components we’re going to use for testing by adding the following item group to the test project file:

Now, we can run dotnet restore on the project or the solution and we can move on to writing tests.

Writing a test to retrieve the page at ‘/’

Now that we have our projects set up, let’s wirte a test that will serve as an example of how other tests will look.

We’re going to start by changing Program.cs in our app project to look like this:

In the snippet above, we’ve changed the method IWebHost BuildWebHost(string[] args) to call a new method IWebHostBuilder CreateWebHostBuilder(string[] args) within it. The reason for this is that we want to allow our tests to configure the IWebHostBuilder in the same way the app does and to allow making changes required by tests. (By chaining calls on the WebHostBuilder.)

One example of this will be setting the content root of the app when we’re running the server in a test. The content root needs to be based on the appliation’s root, not the test’s root.

Now, we can create a test like the one below to get the contents of our home page. This test will fail because we’re missing a couple of things that we describe below.

The test above can be decomposed into the following actions:

  • Create an IWebHostBuilder in the same way that my app creates it
  • Override the content root of the app to point to the app’s project root instead of the bin folder of the test app. (.\src\TestingMVC instead of .\test\TestingMvc.Tests\bin\Debug\netcoreapp2.0)
  • Create a test server from the WebHost builder
  • Create an HttpClient that can be used to communicate with our app. (This uses an internal mechanism that sends the requests in-memory – no network involved.)
  • Send an HTTP request to the server using the client
  • Ensuring the status code of the response is correct

Requirements for Razor views to run on a test context

If we tried to run the test above, we will probably get an HTTP 500 error instead of an HTTP 200 success. The reason for this is that the dependency context of the app is not correctly set up in our tests. In order to fix this, there are a few actions we need to take:

  • Copy the .deps.json file from our app to the bin folder of the testing project
  • Disable shadow copying assemblies

For the first bullet point, we can create a target file like the one below and include in our testing project file as follows:

For the second bullet point, the implementation is dependent on what testing framework we use. For xUnit, add an xunit.runner.json file in the root of the test project (set it to Copy Always) like the one below:

This step is subject to change at any point; for more information look at the xUnit docs at http://xunit.github.io/#documentation.

Now if you re-run the sample test, it will pass.

Summary

  • We’ve seen how to create in-memory tests for an MVC app
  • We’ve discussed the requirements for setting up the app to find static files and find and compile Razor views in the context of a test
  • Set up the content root in the tests to the app’s root folder
  • Ensure the test project references all the assemblies in the app
  • Copy the app’s deps file to the bin folder of the test project
  • Disable shadow copying in your testing framework of choice
  • We’ve shown how to write a functional test in-memory using TestServer and the same configuration your app uses when running on a real server in Production

The source code of the completed project is available here: https://github.com/aspnet/samples/tree/master/samples/aspnetcore/mvc/testing/TestingMVC

Happy testing!