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 😉

ASP.NET Core 2.1 middlewares part 2: Unit test a custom middleware

 

How to unit test a middleware ?

It’s very easy to unit test a middleware in ASP.NET Core.

There three important things to understand here.

First the Invoke method takes in parameter an HttpContext instance.

We all know how it’s painful to mock an HttpContext… We don’t need! ASP.NET Core provide a “fake” HttpContext named DefaultHttpContext. Feeling better now ? 🙂

Second thing to know, if you need to unit test the Response body property you have to initialize it tourslef like this:

var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();

and just before reading the content, reset the body’s stream position back to 0.

context.Response.Body.Seek(0, SeekOrigin.Begin);

And finally, the RequestDelegate object passed in parameter of the middleware’s constructor is simply a delegate, Action exactly.

We are ready to unit test our custom middleware

In these scenarii, we will use XUnit and FluentAssertion

Case 1: scenario with a custom error:

public class CustomExceptionMiddlewareTests
{
   [Fact]
   public async Task WhenACustomExceptionIsRaised_CustomExceptionMiddlewareShouldHandleItToCustomErrorResponseAndCorrectHttpStatus()
   {
      // Arrange
      var middleware = new CustomExceptionMiddleware((innerHttpContext) =>
      {
         throw new NotFoundCustomException("Test", "Test");
      });

      var context = new DefaultHttpContext();
      context.Response.Body = new MemoryStream();

      //Act
      await middleware.Invoke(context);

      context.Response.Body.Seek(0, SeekOrigin.Begin);
      var reader = new StreamReader(context.Response.Body);
      var streamText = reader.ReadToEnd();
      var objResponse = JsonConvert.DeserializeObject<CustomErrorResponse>(streamText);

      //Assert
      objResponse
      .Should()
      .BeEquivalentTo(new CustomErrorResponse { Message = "Test", Description = "Test" });

      context.Response.StatusCode
      .Should()
      .Be((int)HttpStatusCode.NotFound);
   }
}

Case 2: scenario with an unhandled error:

public class CustomExceptionMiddlewareTests
{
   [Fact]
   public async Task WhenAnUnExpectedExceptionIsRaised_CustomExceptionMiddlewareShouldHandleItToCustomErrorResponseAndInternalServerErrorHttpStatus()
   {
      // Arrange
      var middleware = new CustomExceptionMiddleware(next: (innerHttpContext) =>
      {
         throw new Exception("Test");
      });

      var context = new DefaultHttpContext();
      context.Response.Body = new MemoryStream();

      //Act
      await middleware.Invoke(context);

      context.Response.Body.Seek(0, SeekOrigin.Begin);
      var reader = new StreamReader(context.Response.Body);
      var streamText = reader.ReadToEnd();
      var objResponse = JsonConvert.DeserializeObject<CustomErrorResponse>(streamText);

     //Assert
     objResponse
     .Should()
     .BeEquivalentTo(new CustomErrorResponse { Message = "Unexpected error", Description = "Unexpected error" });

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

 

Easy isn’t it ? 😉

How to fix unit test discovery in VS 2017 with MSTest V2?

Introduction

Microsoft Test Framework “MSTest V2” is the evolution of the Microsoft Test Framework and Adapter.

I wanted to try this new framework test by creating a new test project from an empty class library instead of using a MsTest test project and I got in trouble…..

My unit tests were not discovered by Visual Studio 2017.

How did I fix this?

What I have installed

I have installed the following packages:

Then I checked if my unit tests were displayed on Test Explorer and they were not 🙁 , I even tried to run them but I got this message:

What I did to find the issue

There are many reason that can explain this issue, so to know what’s going on I ran the following command line in the unit test project directory:

> dotnet test

It’s the same command that Visual Studio executes but by this way, you will see the precise error if there is an error during execution of the command line:

In my case I knew easily what I missed since the beginning!

I missed the installation of Microsoft.NET.Tests.Sdk package!

I just installed it and I was able to run my tests 🙂

Hope it has helped you 🙂