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 😉

Common features in ASP.NET Core 2.1 WebApi: Logging

 

Introduction

Often overlooked, logging errors and other information in case of bug in the program is an essential feature in a Web API.

In this article we will see how to implement logging in a rolling file in an ASP.NET Core WebAPI with Serilog.

Serilog, Like many other libraries for .NET, Serilog provides diagnostic logging to files, the console, and elsewhere. It is easy to set up, has a clean API, and is portable between recent .NET platforms.

Unlike other logging libraries, Serilog is built with powerful structured event data in mind.

Installation

Download these three packages:

PM> Install-Package Serilog.AspNetCore -Version 2.1.1
PM> Install-Package Serilog.Settings.Configuration -Version 3.0.1
PM> Install-Package Serilog.Sinks.RollingFile -Version 3.3.0

The Serilog.AspNetCore enables Serilog for ASP.NET Core, you have to add Serilog.Settings.Configuration if you need to enable configuration from appsettings.json.

Then Serilog.Sinks.RollingFile enables you to log into rolling files your logs.

Configuring your appsettings.json and Startup.cs

Step 1: Configuring appsettings.json

We chose rolling files to put our logs.

There are three big parts in the configuration

  1. Logging minimum level: In this example the minimum log level is “Information”
  2. Serilog itself: “WriteTo”, as you can see it’s an array, it means you can setup many Sinks (destination of the log), in our case “RollingFile”
  3. Properties: “Application” allows you to name your log if you want to know what log belongs to what application.

Example:

{
 "Serilog": {
    "MinimumLevel": "Information",
    "WriteTo": [
    {
       "Name": "RollingFile",
       "Args": {
          "pathFormat": "C:\\Temp\\log-{Date}.txt",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
       }
    }
   ],
   "Properties": {
      "Application": "Common feature in WebApi demo"
   }
 }
}

Once done we have to go to Startup.cs assigning this configuration to Serilog.

NB: Using settings in appsettings.json is not mandatory, you can build your configuraion by code, you can find examples here.

Step 2: Configuring Startup.cs

As we said before we need to read the configuration from appsettings.json in our example:

public Startup(IConfiguration configuration)
{
   // Init Serilog configuration
   Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
   Configuration = configuration;
}

Then we need to add Serilog to the LoggerFactory in the Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
   // logging
   loggerFactory.AddSerilog();
   app.UseMvc();
 }

In fact Serilog override the default Microsoft Logger provided by the assembly: Microsoft.Extensions.Logging

Logging in action

Let’s create an action that catch an exception and logs it:

[Route("api/[controller]")]
[ApiController]
public class DemoExceptionController : ControllerBase
{
   private readonly ILogger<DemoExceptionController> _logger;

   public DemoExceptionController(ILogger<DemoExceptionController> logger)
   {
      _logger = logger;
   }

   [HttpGet]
   public IEnumerable<string> Get()
   {
      try
      {
         _logger.LogInformation("Could break here :(");
         throw new Exception("bohhhh very bad error");
      }
      catch (Exception e)
      {
         _logger.LogError(e, "It broke :(");
      }
      return new string[] { "value1", "value2" };
   }
}

Let’s see now what happen when this code is executed:

We can see the error log and more (Information) because we set it as minimum level.

Conclusion

This was an example of implementing logging in an ASP.NET Core WebAPI 🙂

Serilog provides many differents ways to log (Sinks), you can find the complete list here.

Serilog is very powerfull, you can also enrich your log easily with Enrichers, a great feature of Serilog!

Awesome right ? 😉

Common features in ASP.NET Core 2.1 WebApi: Error handling

 

Introduction

The exception handling features help us deal with the unforeseen errors which could appear in our code.

In ASP.NET Core we can handle errors in different manners :

  • Error Handling with Try-Catch Block
  • Error Handling with an Exception Filter
  • Handling Errors Globally with the Built-In Middleware (UseExceptionHandler)
  • Handling Errors Globally with the Custom Middleware

In this article we will see how to handle errors with my favourite method: Custom Middleware.

As part of an ASP.NET Core 2.1 application, middleware makes up the processing chain (pipeline) of HTTP requests. They interact on the response and the HTTP request.

Each middleware can call the following middleware. They can perform operations before AND after their successor.

It is important to understand that the HTTP response is built only when the request is passed in this middleware suite.

How to create a middleware

Creating a middleware is really very simple. The IApplicationBuilder interface provides three methods for doing this.

  • Run
  • Map
  • Use

I will just describe in this article the Use method because it allows to create a class we will “use” as a middleware, instead of Run and Map that require an inline declaration.

First, the class must contain an Invoke method that takes an HttpContext parameter and returns a Task. In addition, our class must have a constructor with at least one parameter to use the next middleware (the next method). This one is of type RequestDelegate. However, the constructor may have other parameters. These parameters will be passed to him when adding the middleware in the HTTP pipeline. This addition is done using the UseMiddleware method.

Example with a custom middleware that handle errors:

public class CustomExceptionMiddleware
{
   private readonly RequestDelegate _next;

   public CustomExceptionMiddleware(RequestDelegate next)
   {
      _next = next;
   }

   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 customException = exception as BaseCustomException;
      var statusCode = (int)HttpStatusCode.InternalServerError;
      var message = "Unexpected error";
      var description = "Unexpected error";

      if (null != customException)
      {
         message = customException.Message;
         description = customException.Description;
         statusCode = customException.Code;
      }

      response.ContentType = "application/json";
      response.StatusCode = statusCode;
      await response.WriteAsync(JsonConvert.SerializeObject(new CustomErrorResponse
      {
         Message = message,
         Description = description
      }));
   }
}

This middleware handle all errors and control the answer to the client: it sends a custom error message.

There are 2 cases :

First case: it’s a custom error that we know what to do with and what to send to the client BaseCustomException

Second case: it’s an unmanaged error that we don’t know what to do with, we will send a generic error message to the client.

Here is the complete implementation of custom errors, DTO and exception raising:

public class BaseCustomException : Exception
{
   private int _code;
   private string _description;

   public int Code
   {
      get => _code;
   }
   public string Description
   {
      get => _description;
   }

   public BaseCustomException(string message, string description, int code) : base(message)
   {
      _code = code;
      _description = description;
   }
}
public class CustomErrorResponse
{
   public string Message { get; set; }
   public string Description { get; set; }
}
public class NotFoundCustomException : BaseCustomException
{
   public NotFoundCustomException(string message, string description) : base(message, description, (int)HttpStatusCode.NotFound)
   {
   }
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
   [HttpGet]
   public ActionResult<IEnumerable<string>> Get()
   {
      throw new Exception();
      return new string[] { "value1", "value2" };
   }

   [HttpGet("{id}")]
   public ActionResult<string> Get(int id)
   {
      throw new NotFoundCustomException("No data found", $"Please check your parameters id: {id}");
      return "value";
   }
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDeveloperExceptionPage();
   }
   app.UseMiddleware<CustomExceptionMiddleware>();
   app.UseMvc();
}

Demo

Case 1:

Unhandled error scenario

Case 2:

Custom error scenario

Conclusion

This was an example of implementing error handling in an ASP.NET Core WebAPI 🙂

Nice isn’t it ? 😉