On va voir comment avoir en quelques minutes des assertions qui vont vérifier les endpoints de notre API avec des scénarios de ce genre:
Feature: Create a new account
As a visitor,
I can create an account to access the game
Scenario: A visitor creates an account
When I fill the login form with
| email | password |
| [email protected] | Jh0nD0e! |
Then I can login with
| email | password |
| [email protected] | Jh0nD0e! |
Et des assertions du style:
[When("I fill the login form with")]
public async Task IFillTheLoginForm(Table table)
{
var row = table.Rows[0];
RegisterUserCommand registerUserCommand = new (row["email"], row["password"]);
var result = await _httpClient.PostAsJsonAsync("/api/users/register", registerUserCommand);
result.EnsureSuccessStatusCode();
}
Si vous utilisez Rider et l’extension Reqnroll, vous pouvez simplement créer un nouveau projet Reqnrol depuis le template et en quelques clics c’est fait !
2. Installation des dépendances
On va rajouter la dépendance à Microsoft.AspNetCore.Mvc.Testing dans notre projet Reqnroll. C’est elle qui va nous permettre d’avoir un client Http très facilement.
II. Configuration
Il va falloir dire qu’on veut lancer le client de test sur notre projet qui contient notre API. Pour se faire, on va dans le program.cs de notre API et on rajoute cette ligne tout à la fin:
public partial class Program { }
On va ensuite créer notre fichier contenant la définition de nos steps (UserStepDefinition.cs dans mon cas) puis initialiser notre client Http via le constructeur de notre classe:
[Binding]
public sealed class UserStepDefinitions
{
private readonly HttpClient _httpClient;
public UserStepDefinitions(WebApplicationFactory<Program> factory)
{
_httpClient = factory.CreateClient(new WebApplicationFactoryClientOptions());
}
}
Et c’est tout ! On peut dès à présent faire des vérifications sur nos endpoints.
Voici ma classe entière qui permet de lancer le scénario présenté en introduction:
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.Data;
using Microsoft.AspNetCore.Mvc.Testing;
using Reqnroll;
using StrategyBrowserGameAPI.Command;
namespace AcceptanceTests.StepDefinitions;
[Binding]
public sealed class UserStepDefinitions
{
private readonly HttpClient _httpClient;
public UserStepDefinitions(WebApplicationFactory<Program> factory)
{
_httpClient = factory.CreateClient(new WebApplicationFactoryClientOptions());
}
[When("I fill the login form with")]
public async Task IFillTheLoginForm(Table table)
{
var row = table.Rows[0];
RegisterUserCommand registerUserCommand = new (row["email"], row["password"]);
var result = await _httpClient.PostAsJsonAsync("/api/users/register", registerUserCommand);
result.EnsureSuccessStatusCode();
}
[Then("I can login with")]
public async Task ICanLoginWith(Table table)
{
var row = table.Rows[0];
var loginRequest = new LoginRequest
{
Email = row["email"],
Password = row["password"]
};
var loginResponse = await _httpClient.PostAsJsonAsync("/login", loginRequest);
loginResponse.EnsureSuccessStatusCode();
var accessToken = await ExtractAccessToken(loginResponse);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var manageInfoResult = await _httpClient.GetAsync("/manage/info");
manageInfoResult.EnsureSuccessStatusCode();
}
private static async Task<string> ExtractAccessToken(HttpResponseMessage loginResponse)
{
var loginResponseAsString = await loginResponse.Content.ReadAsStringAsync();
var jsonDoc = JsonDocument.Parse(loginResponseAsString);
var accessToken = jsonDoc.RootElement.GetProperty("accessToken").GetString();
return accessToken;
}
}
Pour aller plus loin
Évidemment, dans mon cas très simple avec une BDD in memory et sans appels externes ça marche très bien mais dans vos applications professionnelles, vous avez sûrement un peu plus de dépendances que ça.
La lib Microsoft.AspNetCore.Mvc.Testing est bien faite et permet facilement d’ajouter de la personnalisation: peut-être que vous souhaitez utiliser une base in-memory pour les tests ou un substitut pour un service externe par exemple.
On ne l’abordera pas dans cet article mais ces exemples sont détaillés dans la doc officielle .Net.
Avant .Net 8, tester du code qui utilise DateTime.Now() n’était pas trivial, on devait faire en sorte de mocker la Clock dans nos tests. Depuis .Net 8, c’est beaucoup plus facile grâce à TimeProvider inclut par défaut !
En deux mots, TimeProvider est une classe abstraite et on va pouvoir injecter dans nos tests une implémentation “Fake” qui va nous permettre de changer la date actu...
La gestion des erreurs de son API est très importante pour que les consommateurs puissent avoir une description claire du problème mais c’est souvent fastidieux à maintenir.
On va voir comment avoir des statuts de réponse cohérent et des messages d’erre...
Depuis .NET 9, le le support d’OpenAPI est directement inclus dans .NET et ne passe plus par les librairies Swagger par défaut (plus d’info sur ce choix ici si jamais ça vous intéresse).
De façons simplifiée, la librairie Swashbuckle.AspNetCore.Sw...
Description du problème
Par défaut, il n’est pas autorisé de faire des requêtes entre une application qui est dans un domaine A vers une autre qui serait dans un domaine B (pour des raisons de sécurité, il y a plus de détails dans les sources).
S...