2-6-2 Test d'une requête POST d'un formulaire
Nous avons des utilisateurs et bientôt nous testerons des requêtes en tant qu'utilisateur connecté, donc le prétexte est bon pour tester la connexion à l'application.
Nous verrons d'un côté comment tester une requête POST
envoyant des données de formulaire et le code que nous allons écrire nous sera utile plus loin dans le contexte de tester des requêtes en tant qu'utilisateur authentifié.
Créer un fichier de tests pour l'authentification
- Sous
Web.Mvc/TestsIntegration
->Add
->Class/Interface
- Nommer la classe
AuthTests
- Faites hériter la classe de
WebMvcIntegrationTestBase
et créez le constructeurSnowfall.Tests/Web.Mvc/TestsIntegration/AuthTests.cspublic class AuthTests : WebMvcIntegrationTestBase
{
public AuthTests(
SnowfallMvcApplicationFactory application,
TestDatabaseFixture database) : base(application, database)
{
}
}
Créer une fonction de test pour la connexion
[Fact]
public async Task Connexion_AuthentificationSucces_RetourneRedirectionAccueil()
{
}
- action:
Connexion
- scénario:
AuthentificationSucces
(l'authentification est réussie) - résultat attendu:
RetourneRedirectionAccueil
(redirige vers l'accueil)
Tester la connexion
Allons-y, on peut tester la connexion!
-
Dans un premier temps, on crée le client HTTP
Snowfall.Tests/Web.Mvc/TestsIntegration/AuthTests.cs[Fact]
public async Task Connexion_AuthentificationSucces_RetourneRedirectionAccueil()
{
// Arrange
var client = Application.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
}infoPar défaut, le client HTTP suit les redirections. Par exemple, après la connexion, l'utilisateur est redirigé vers la page d'accueil.
On veut premièrement tester, entre autres, qu'une redirection est retournée suite à la connexion et donc que la connexion semble être un succès. Si on suit automatiquement les redirections, cette information est perdue.
On crée notre propre client plutôt que celui de la classe de base puisqu'on doit passer une option personnalisée.
-
Ensuite, on appelle la page de connexion pour obtenir la page de connexion et vérifier que la page contenant le formulaire retourne bien un code de succès (ex.: 200 OK).
Snowfall.Tests/Web.Mvc/TestsIntegration/AuthTests.cs[Fact]
public async Task Connexion_AuthentificationSucces_RetourneRedirectionAccueil()
{
// Arrange
var client = Application.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
HttpResponseMessage reponse = await client.GetAsync("Auth/Connexion");
reponse.EnsureSuccessStatusCode();
} -
Puis, on bâtit les informations qui seront envoyées via le formulaire. Ces dernières doivent être construites sous forme de
Dictionary
, soit sous forme d'objet clé-valeur:Snowfall.Tests/Web.Mvc/TestsIntegration/AuthTests.cs[Fact]
public async Task Connexion_AuthentificationSucces_RetourneRedirectionAccueil()
{
// Arrange
var client = Application.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
HttpResponseMessage reponse = await client.GetAsync("Auth/Connexion");
reponse.EnsureSuccessStatusCode();
var connexionViewModelDictionary = new Dictionary<string, string>
{
{ "Email", "u@ser.com" },
{ "Password", "!User122432" }
};
}infoOn fera une requête vers la route
/auth
, associée au contrôleur d'authentification, acceptant en paramètre unViewModel
de connexion. C'est pourquoi nous créons un dictionnaire calqué sur le format deConnexionViewModel
. -
Pour enfin faire un
POST
sur l'action du formulaire de connexion avec les données de connexion. On utiliseFormUrlEncodedContent
, qui prend en entrée un dictionnaire, pour encoder les valeurs.Snowfall.Tests/Web.Mvc/TestsIntegration/AuthTests.cs[Fact]
public async Task Connexion_AuthentificationSucces_RetourneRedirectionAccueil()
{
// Arrange
var client = Application.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
HttpResponseMessage reponse = await client.GetAsync("Auth/Connexion");
reponse.EnsureSuccessStatusCode();
var connexionViewModelDictionary = new Dictionary<string, string>
{
{ "Email", "u@ser.com" },
{ "Password", "!User122432" }
};
// Act
reponse = await client.PostAsync(
"Auth",
new FormUrlEncodedContent(connexionViewModelDictionary));
} -
Finalement, on teste que le retour est bien une redirection et que la redirection est bien effectuée vers l'accueil.
Snowfall.Tests/Web.Mvc/TestsIntegration/AuthTests.cs[Fact]
public async Task Connexion_AuthentificationSucces_RetourneRedirectionAccueil()
{
// Arrange
var client = Application.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
HttpResponseMessage reponse = await client.GetAsync("Auth/Connexion");
reponse.EnsureSuccessStatusCode();
var connexionViewModelDictionary = new Dictionary<string, string>
{
{ "Email", "u@ser.com" },
{ "Password", "!User122432" }
};
// Act
reponse = await client.PostAsync(
"Auth",
new FormUrlEncodedContent(connexionViewModelDictionary));
// Assert
Assert.Equal(HttpStatusCode.Redirect, reponse.StatusCode);
Assert.Equal("/",reponse.Headers.Location?.OriginalString);
}
Exécuter le test
Si vous exécutez le test, vous obtiendrez un échec du test:
Assert.Equal() Failure
Expected: Redirect
Actual: BadRequest
Plutôt qu'une redirection, on obtient une réponse BadRequest
🤔.
Il nous manque le jeton CSRF
qui valide la provenance de la requête!
Avant d'envoyer le formulaire, il nous faudra extraire le jeton CSRF
de ce dernier et l'envoyer avec la requête.
Obtenir le jeton CSRF
Installer AngleSharp
Pour obtenir le jeton, on il est possible d'utiliser une librairie qui facilite la lecture du HTML reçu. La librairie que nous utiliserons est AngleSharp
.
- Sous le projet
Snowfall.Tests
->Manage NuGet Packages
- Faire une recherche pour
AngleSharp
- Installer la librairie dans le projet
Ajouter la classe utilitaire HtmlHelper
Microsoft propose une classe utilitaire pour lire une réponse HTTP et la convertir en document IHtmlDocument
AngleSharp (réf: https://github.com/dotnet/AspNetCore.Docs.Samples/blob/main/test/integration-tests/IntegrationTestsSample/tests/RazorPagesProject.Tests/Helpers/HtmlHelpers.cs). Réutilisons cette classe.
- Sous le projet
Snowfall.Tests
, créez un dossierHelpers
- Sous le dossier
Helpers
du projetSnowfall.Tests
->Add
->Class/Interface
- Nommez la classe
HtmlHelpers
et ajoutez-y le code suivant:Snowfall.Tests/Helpers/HtmlHelpers.cspublic class HtmlHelpers
{
public static async Task<IHtmlDocument> GetDocumentAsync(HttpResponseMessage response)
{
var content = await response.Content.ReadAsStringAsync();
var document = await BrowsingContext.New()
.OpenAsync(ResponseFactory, CancellationToken.None);
return (IHtmlDocument)document;
void ResponseFactory(VirtualResponse htmlResponse)
{
htmlResponse
.Address(response.RequestMessage.RequestUri)
.Status(response.StatusCode);
MapHeaders(response.Headers);
MapHeaders(response.Content.Headers);
htmlResponse.Content(content);
void MapHeaders(HttpHeaders headers)
{
foreach (var header in headers)
{
foreach (var value in header.Value)
{
htmlResponse.Header(header.Key, value);
}
}
}
}
}
}
Récupérer le jeton de la réponse
On peut finalement récupérer le jeton et l'inclure dans le dictionnaire envoyé en utilisant la nouvelle classe utilitaire HtmlHelpers
. Le principe ici est que le jeton CSRF
est toujours nommé __RequestVerificationToken
dans un formulaire .NET MVC
. On cherche donc dans la page une balise input
de ce nom et on en extrait la valeur.
[Fact]
public async Task Connexion_AuthentificationSucces_RetourneRedirectionAccueil()
{
// Arrange
var client = Application.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
HttpResponseMessage reponse = await client.GetAsync("Auth/Connexion");
reponse.EnsureSuccessStatusCode();
var document = await HtmlHelpers.GetDocumentAsync(reponse);
var csrfInput = document.QuerySelector("input[name='__RequestVerificationToken']") as IHtmlInputElement;
string? csrf = csrfInput?.Value;
var connexionViewModelDictionary = new Dictionary<string, string>
{
{ "Email", "u@ser.com" },
{ "Password", "!User122432" },
{ "__RequestVerificationToken", csrf }
};
// Act
reponse = await client.PostAsync(
"Auth",
new FormUrlEncodedContent(connexionViewModelDictionary));
// Assert
Assert.Equal(HttpStatusCode.Redirect, reponse.StatusCode);
Assert.Equal("/",reponse.Headers.Location?.OriginalString);
}
Test
Vous pouvez tester et le test devrait finalement passer!
Pour être certain hors de tout doute que l'utilisateur est bien connecté, on peut charger la page d'accueil à la fin du test et vérifier que le nom de l'utilisateur est bien dans la page.
//...
// Assert
Assert.Equal(HttpStatusCode.Redirect, reponse.StatusCode);
Assert.Equal("/",reponse.Headers.Location?.OriginalString);
reponse = await client.GetAsync(reponse.Headers.Location.ToString());
Assert.Contains("Bonjour, Uti", await reponse.Content.ReadAsStringAsync());
}