Integration testing with .NET Core
How backend can make QA’s life easier
This article is based on my personal experience in backend development for Web API sites. Now I want to share it with other people.
When I’m speaking about integrartion testing I mean testing all components of the single Web API application. It often happens during the regression testing of the product performed by QA.
Possible approaches
There are several different approaches which can be used to perform the testing.
Handmade implementation
The most obvious way of performing integration testing of Web API application is sending request to instance of the application via Postman or another application with similar abilities and comparing responses with expected results.
Unfortunately it isn’t the best solution because it has several obvious disadvantages. Firstly, it takes a lot of time that means very slow developing process. Secondly, it is unreliable because human can forget something, can miss something, etc. Thirdly, you have to store information about product expected behavior in some form. It can be very simple and understandable by everyone, but in this case it can’t be rapidly changed, or it can be stored in very compact format and understandable by only a few people that is the cause of high bus factor.
Automated implementation
Many of mentioned problems can be solved by using tests automation. Well-written tests make the testing process much more stable and its results more predictable. Moreother, well-written tests can dramatically increase understanding of the operating principles of the system and reduce bus factor. For implementation of this approach a client of the tested service and a set of tests written in any programming language are needed.
Of course this approach solves a lot of problems mentioned early, but still it has some disadvantages. Firstly, running all tests still requires hours or even days because network interactions are very slow. Secondly, the instance of the tested service should be properly hosted in some environment, it can be developer’s computer or special test environment so it will be quite difficult to debug it if something goes wrong. Thirdly, there are still some application artifacts which correctness can be checked only by QA (for example, written logs, sent emails, messages pushed to queue). In this way testing process can’t be fully automated, QA still have to check some behavior manually, and it takes time.
Extended automated implementation
In the matter of fact, almost all problems mentioned in previous section are caused by application hosting. In fact, the necessity of manually checking part of the program’s work results is caused by the lack of the ability to replace the standard dependencies of the hosted system (such as logger, email client or queue client) with testable mocks. And of course the problem of the slow network interactions will be able to solve easily if client interacts with application directly in memory. Fortunately, now there is the special TestServer class which can solve all of these problems.
In this approach tested application is hosted via TestServer, special server for tests purposes which performs Api methods calls directly in memory (it is avaliable in NuGet package Microsoft.AspNetCore.TestHost) and shared between all tests. Furthermore it is easy to debug tested application and change some of its dependencies because now Web API is hosted inside testing application. This briefly formulated approach contains many unexplained technical details, which will be minutely discussed in subsequent sections.
Technical details of the last implementation
Some small but important implementation details weren’t mentioned in previous section, so now they’ll be explained.
Why xUnit?
Hosting Web API site even via TestServer is an expensive operation. Moreover, several migration processes runned simualteniosly can corrupt the application environment. So its strictly necessary to setup one instance of the application, share it between all tests and dispose when all tests will pass. xUnit allows to do this by using collection fixtures. To use this approach folowing steps should be taken:
- A fixture class is created in the constructor of which the application hosting logic via the TestServer is located, here some of application dependencies can be changed.
- In the fixture class interface
IDisposable
is implemented to dispose the hosted application after all tests will pass. - A tests collection definition class is created and decorated with
[CollectionDefinition]
сontaining a unique name of the tests collection. - The collection definition class is inherited from
ICollectionFixture<>
interface with fixture class name passed as generic parameter to indicate the needed fixture. - Classes with tests are created and decorated with
[Collection]
attribute containing the name of the tests collection. - The instance of the fixture class is passed into tests classes as the constructor argument.
Of course this is not exactly integration testing because implementations of some standart contracts were changed (contacts of Logger, EmailClient and QueueClient) but they are usually developed and tested by other developers so this can’t do any harm.
Connection between client and server
The simplest way of establishing connection between Web API client and TestServer is to create the instance of the clinet inside the constructor of the tests class and pass the instance of HttpMessageHandler
to it. Instance of HttpMessageHandler
can be provided by method CreateHandler
of the TestServer.
Code example
Base fixture for Web API tests collection
Base class for tests
Tests collection definition class
Web API tests example
References
Tools:
Documentation:
- https://docs.microsoft.com/en-us/aspnet/core/testing/integration-testing;
- https://andrewlock.net/introduction-to-integration-testing-with-xunit-and-testserver-in-asp-net-core/;
- https://xunit.github.io/docs/shared-context.html#collection-fixture.