SHARE:

Common features in ASP.NET Core 2.1 WebApi: Testing

 

Introduction

Often neglected as well, unit tests make it possible to verify that an implementation remains in conformity with the expected operating rule, preventing regressions in case of evolution of the program.

The integration tests, they are used to verify what the unit tests can not do: test the operation of each layer of software between them.

In this article we will use these tools:

  • FluentAssertion
    • Fluent Assertions is a set of .NET extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style test. This enables, simple intuitive syntax
  • XUnit
    • xUnit.net is a free, open source, community-focused unit testing tool for the .NET Framework. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit testing C#, F#, VB.NET and other .NET languages. xUnit.net works with ReSharper, CodeRush, TestDriven.NET and Xamarin. It is part of the .NET Foundation, and operates under their code of conduct. It is licensed under Apache 2 (an OSI approved license).
  • NSubstitute
    • NSubstitute is a friendly substitute for .NET mocking frameworks
  • TestServer
    • ASP.NET Core includes a test host that can be added to integration test projects and used to host ASP.NET Core applications, serving test requests without the need for a real web host.

Installation

Download these packages:

PM> Install-Package FluentAssertions -Version 5.5.3
PM> Install-Package xunit -Version 2.4.1
PM> Install-Package xunit.runner.console -Version 2.4.1
PM> Install-Package NSubstitute -Version 3.1.0
PM> Install-Package Microsoft.AspNetCore.TestHost -Version 2.1.1

Creating unit tests

I have already written some articles on this subject. They show how to make unit tests with XUnit, FluentAssertion and NSubstitute.

They are fully applicable on ASP.NET Core 2.1 WebAPI:

I did not use NSubstitute in these two previous articles then I reused the middleware built in this article (ASP.NET Core 2.1 middlewares part 2: Unit test a custom middleware) to show you how NSubstitute works:

Middleware to unit test:

public class CustomExceptionMiddleware
{
   private readonly RequestDelegate _next;
   private readonly ILogger<CustomExceptionMiddleware> _logger;

   public CustomExceptionMiddleware(RequestDelegate next, ILogger<CustomExceptionMiddleware> logger)
   {
      _next = next;
      _logger = logger;
   }

   public async Task Invoke(HttpContext context)
   {
      try
      {
         await _next.Invoke(context);
      }
      catch (Exception ex)
      {
         await HandleExceptionAsync(context, ex);
      }
   }

   private async Task HandleExceptionAsync(HttpContext context, Exception exception)
   {
      var response = context.Response;
      var message = "Unhandled error";
      var code = "00009";
      var errors = new List<string>();

      response.ContentType = "application/json";
      response.StatusCode = (int)HttpStatusCode.InternalServerError;

      // log generic exception message (original error)
      _logger.LogError(exception, exception.Message);
 
      // Response
      await response.WriteAsync(JsonConvert.SerializeObject(new Error
      {
         Code = code,
         Message = message,
         Errors = errors
      }));
   }
}

Middleware unit tested:

[Fact]
public async Task WhenAGenericExceptionIsRaised_CustomExceptionMiddlewareShouldHandleItToDefaultErrorResponseAndLoggerCalled()
{
   // Arrange
   var loggerMock = Substitute.For<ILogger<CustomExceptionMiddleware>>();
   var middleware = new CustomExceptionMiddleware((innerHttpContext) =>
   {
      throw new Exception("Oooops error!");
   }, loggerMock);

   // Use DefaultHttpContext insteadof mocking HttpContext
   var context = new DefaultHttpContext();

   // Initialize response body
   context.Response.Body = new MemoryStream();

   //Act
   await middleware.Invoke(context);

   // set the position to beginning before reading
   context.Response.Body.Seek(0, SeekOrigin.Begin);

   // Read the response
   var reader = new StreamReader(context.Response.Body);
   var streamText = reader.ReadToEnd();
   var objResponse = JsonConvert.DeserializeObject<Error>(streamText);

   //Assert
   objResponse
   .Should()
   .BeEquivalentTo(new { Message = "Unhandled error", Errors = new List<string>() , Code = "00009" });

   context.Response.StatusCode
   .Should()
   .Be((int)HttpStatusCode.InternalServerError);

   loggerMock.Received(1);
}

Simple right 😉 ?

Creating integration tests with TestServer

Emulate a server

The first step before testing WebAPI is to build an emulated server by using the class TestServer provided by the assembly: Microsoft.AspNetCore.TestHost

Example:

public class TestServerFixture : IDisposable
{
   private readonly TestServer _testServer;
   public HttpClient Client { get; }

   public TestServerFixture()
   {
      var builder = new WebHostBuilder()
      .UseEnvironment("Development")
      .UseStartup<Startup>();

      _testServer = new TestServer(builder);
      Client = _testServer.CreateClient();
   }

   public void Dispose()
   {
      Client.Dispose();
      _testServer.Dispose();
   }
}

WebHostBuilder is easy to setup, you just need to setup the environment you want to use for your tests, and delcare the Startup class you want to use (Startup of your WebAPI obviously).

Then you just have to assign to a HttpClient variable an instance of a TestServer class that take in parameters the WebHostBuilder previously defined.

That’s it!

Now let’s see how to test an URI protected by an Autorization by a bearer token for which no token is sent, we expect an Http 401 UnAuthorized:

[Fact]
public async Task WhenGetMethodIsInvokedWithoutAValidToken_GetShouldAnswerUnAuthorized()
{
   using (TestServerFixture fixture = new TestServerFixture())
   {
      // Act
      var response = await fixture.Client.GetAsync("/api/DemoAuthorization/5");

     // Assert
     response
     .StatusCode
     .Should()
     .Be(HttpStatusCode.Unauthorized);
   }
}

Test Server is really easy to consume, it’s a disposable resource, using {} is the common way to consume it and dispose it after its use.

Conclusion

You saw how to make easily unit tests and integration tests.

That’s a very big feature that you must implement when you develop a program such as a WebAPI.

Don’t forget this 😉

Written by

anthonygiretti

Anthony is a specialist in Web technologies (14 years of experience), in particular Microsoft .NET and learns the Cloud Azure platform. He has received twice the Microsoft MVP award and he is also certified Microsoft MCSD and Azure Fundamentals.