Alica's dev blog
Integration tests with ASP.NET Core and SQL Server - Part 4: Make HTTP requests to the API

We now have the database ready, thanks to Part 2: Create the database and Part 3: Seed and change the database. Now let’s move on to the other end and find out how to call the API.

The Why

If we have an API with multiple levels of abstraction (which is almost always the case), then surely it makes sense to test the layers one by one (not subject of this post), but also test if they work together in harmony.

When there is somebody (e.g. SPA) that consumes the API, then the only thing that matters is the result that the API returns.

It happened to me that the logic layer calculated everything correctly but because of a wrong if-else call in the controller the actual result was wrong.

It’s nice if the frontend developer tells you where they are getting the error and you fix it, but this is probably the most expensive testing method. (Or rather the second most expensive, the top one is putting the code to production and letting the customers discover bugs themselves. Sounds irrational, but I’ve already seen that during my short career.)

Therefore we want to make sure that we catch as much bugs as possible before anybody uses the API. Fortunately, it’s not as complicated as it might seem and ASP.NET Core provides handy tooling for that.

I can imagine that it might get more complicated if the API requires authentication. I am optimistic and assume that it is a solvable problem, but I haven’t tried that myself yet. However, I expect to do so in near future and will update the post accordingly.

The How

Create the API client

Let’s start with running the API. Our goal is to obtain an instance of HttpClient (from System.Net.Http) that we will use for sending the requests.

One way is to instantiate a TestServer (from Microsoft.AspNetCore.TestHost).

var webHostBuilder = WebHost
	.CreateDefaultBuilder<Startup>(Array.Empty<string>());
var server = new TestServer(webHostBuilder);
HttpClient client = server.CreateClient();

However, this approach has a disadvantage: if you want the Startup class either to use different configuration (like different connection string), or to add or remove services, you need to put those pieces of code into virtual methods and then override them in an inherited class (e.g. TestStartup).

Plus (assuming that tests and TestStartup are in a different assembly than the API), you need to register the controllers in the overriden class: by default, only the controllers from the current assembly are registered.

So, let’s not go into more details here, and rather move on to the second approach that I consider better.

Instead of creating TestServer, we will use WebApplicationFactory (from Microsoft.AspNetCore.Mvc.Testing).

The basic setup is just two lines:

var factory = new WebApplicationFactory<Startup>();
HttpClient client = factory.CreateClient();

If we want some customization, we will create an inherited class TestApplicationFactory, override ConfigureWebHost method and do the setup there. The important thing is that we don’t need to touch Startup class at all (as we would have to with the first approach).

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
	builder.ConfigureAppConfiguration((context, configurationBuilder) =>
	{
		// add custom configuration
		var path = AppDomain.CurrentDomain.BaseDirectory;
        configurationBuilder.AddJsonFile(Path.Combine(path, "appsettings.Test.json"));
	});

	builder.ConfigureServices((context, collection) =>
	{
		// read from the configuration
		var connectionString = context.Configuration.GetConnectionString("key");
		
		// do something with it
		// ...
	});
}

The documentation also provides an example on how to add or remove services. As I mentioned in the beginning, adding authentication might pose a complication for our test setup – and I assume that this would be a way to deal with it.

Edit (01/2022): Now I have an article dedicated to this topic: Configuring mock services in WebApplicationFactory.

Send the requests

That’s as easy as

var responseMessage = client.GetAsync("/api/something");

or

var responseMessage = client.PostAsJsonAsync("/api/something", value);

Read the responses

Now we want to check whether the response is correct. However, the responseMessage variable is of type Task<HttpResponseMessage>, therefore we need to dig into it and get the status code and the content.

HttpResponseMessage message = messageTask.Result;
HttpStatusCode statusCode = message.StatusCode;
MyViewModel content = message.Content.ReadAsAsync<MyViewModel>().Result;

I know it’s probably an antipattern to use .Result property, but I think that our use case justifies it.

Note: ReadAsAsync<T> method is provided by Microsoft.AspNet.WebApi.Client package.

Make assertions

We need to read the response at least once in every test method, so repeating those 3 lines again and again isn’t a way to go.

Let’s put it into two extension methods that

  • check whether the status code is as expected (using FluentAssertions)
  • return the content if there should be any
public static T AssertContent<T>(this Task<HttpResponseMessage> messageTask, HttpStatusCode expectedStatusCode)
{
	var message = messageTask.Result;
	message.StatusCode.Should().Be(expectedStatusCode);
	return message.Content.ReadAsAsync<T>().Result;
}

public static void AssertEmpty(this Task<HttpResponseMessage> messageTask, HttpStatusCode expectedStatusCode)
{
	var message = messageTask.Result;
	message.StatusCode.Should().Be(expectedStatusCode);
}

I only use a limited number of status codes in my API, so I added a couple of methods that assert concrete status codes:

// expects 200 and content
public static T AssertOk<T>(this Task<HttpResponseMessage> messageTask) 
	=> AssertContent<T>(messageTask, HttpStatusCode.OK);

// expects 201 and content
public static T AssertCreated<T>(this Task<HttpResponseMessage> messageTask) 
	=> AssertContent<T>(messageTask, HttpStatusCode.Created);
	
// expects 204 and no content
public static void AssertNoContent(this Task<HttpResponseMessage> messageTask) 
	=> AssertEmpty(messageTask, HttpStatusCode.NoContent);

// expects 404 and no content
public static void AssertNotFound(this Task<HttpResponseMessage> messageTask) 
	=> AssertEmpty(messageTask, HttpStatusCode.NotFound);

// etc.

Source Code

You can view the complete source code on Github.

What’s next

Read the next posts in the series:

The previous ones are:


Last modified on 2020-10-30