Giraffe is a powerful framework that helps us to build web application and services using F# and .NET Core. The following article shows how to test a giraffe web service.
Since last few months I started to focus on F# world, I have write about web development through F# in following articles: Web development in F#: getting started and Build web service using F# and ASP.NET Core;
This article takes the following repo https://github.com/samueleresca/Blog.FSharpOnWeb as case of study.
Testing concepts from OO
When we think about unit testing, some keywords in on our minds are: dependency injection, IoC and mock.
Speaking about FP, it is totally counterproductive try to apply OO concepts to functional approach, therefore the functional approach requires a way to support unit testing, in OOP it is common to isolate I/O-related operations into dependencies that can be mocked easily, thus FP allows to handle dependencies through concepts like: parameters injection and composition.
For example, let’s take the following implementation:
As you can see, all the functions take as input the
LabelsContext type. The
LabelsContext is high coupled with our data source. Since we want to make our code testable, we must expose the context as input of our function.
Testing a Giraffe web service
Since Giraffe is based on ASP.NET Core, most of the testing tools of ASP.NET Core can be transferred in Giraffe testing. Before start, I recommend you to check the implementation described in this article: Build web service using F# and ASP.NET Core.
Following example shows how to test a Giraffe web service at different levels:
Repositorylevel: we will use the
Microsoft.EntityFrameworkCore.InMemorypackage in order to test our repository emulating the underlying data source;
Handlerlevel: we will mock the
HttpContextin order to call our handlers and check the resulting response, also in that case we will use the
Microsoft.EntityFrameworkCore.InMemoryto emulate our data source;
- Integration level: in order to perform some integration tests we’ll setup the
Microsoft.AspNetCore.TestHostpackage and call through an
HttpClientthe APIs exposed by our web service;
Testing repository level
Let’s start by testing the repository level of our service. As said before the
Repository level is implemented in that way:
We can use the injected
HttpContext in order to test our
Repository. Through the use of the
Microsoft.EntityFrameworkCore.InMemory package we can inject an in-memory context:
Testing handler level
The handler layer aim is to handle incoming http requests and serve correct resources. All the handlers are implemented in the
Each handler is mapped on a specific route, each route may accept incoming data and after that, handlers trigger the request validation and they pass data through ours repository functions. The following snippet shows the route mapping of our application:
Since we want to test our handlers, we should proceed as follows:
populateContextin order to store our test data in memory;
- setup some utility functions like:
configureContext. They are useful in order to execute our tests;
- execute tests of our suite;
Let’s take a look to our
HttpContext is the base building block of our tests, each of them prepares the request inside the context and pass it to our handlers. In order to obtain the same routing map of our application, each test will reference
App.webApp routing. Let put the focus on a single test case:
First step is to define our in-memory context. Secondly, we serialize our test data and configure our context. Finally, we put all the values inside our request.
Once the arrange phase is finished we proceed by stressing and acting over our function under test, and then we proceed with the arrange phase, by checking the response.
Test host testing
Another approach is to perform tests by using the
Microsoft.AspNetCore.TestHost package. The main aim of the
TestHost package is to serve test requests without the need for a real web host.
Since we are testing our service in an almost real infrastructure we must prepare our service to deal with a Testing environment. Thus we update our
Program.fs file as follow:
configureServices change their behaviour according to the current environment: in that case, the
configureServices function uses
InMemory provider when the current environment is
Test, otherwise it use the default provider.
This is an implementation example of the test host approach:
Since we are performing a
TestHost test, each test generates a new
createHost method, and create a new client. The client will call the corresponding route and it will make some assertions on the
You have seen how to tests some Giraffe APIs. We have tested some CRUD operations on database by using
Microsoft.EntityFrameworkCore.InMemory package which simulates a real database.
By the way, the
InMemory packages is not always a valid solution, indeed we may encounter different problems:
- Your tests are not isolated until they use the same
InMemorypackage doesn’t exactly reflect the constraint of your real database;
If you have a codebase which implements more advanced logics than some CRUD operations the aim should be to keep functions as pure as possibile. Furthermore, the best way to test and describe logics is by using an unit test approach.
You can find all code inside the following repository. In the test project you also find some fixture files:
Fixture.fswhich contains some utilities function;
HttpFunc.fsimplements some functions to deal with
That article describes a possible testing approach on Giraffe framework. I suggest you to take a look @ Build web service using F# and ASP.NET Core since it is strictly related to it.
I also suggest you to watch the following talk about Functional design patterns in F# in order to make your code more testable and maintainable.
Cover image by Peter Iliev