Common features in ASP.NET Core 2.1 WebApi: Testing
- Common features in ASP.NET Core 2.1 WebApi: Authenticating with a JWT
- Common features in ASP.NET Core 2.1 WebApi: Validation
- Common features in ASP.NET Core 2.1 WebApi: Error handling
- Common features in ASP.NET Core 2.1 WebApi: Logging
- Common features in ASP.NET Core 2.1 WebApi: Testing
- Common features in ASP.NET Core 2.1 WebApi: Documenting
- Common features in ASP.NET Core 2.2 WebApi: Profiling
- Common features in ASP.NET Core 2.2 WebApi: Caching
- Common features in ASP.NET Core 2.2 WebApi: Mapping
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:
- Code reliability: Unit testing with XUnit and FluentAssertions in .NET Core 2 apps
- ASP.NET Core 2.1 middlewares part 2: Unit test a custom middleware
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 😉