Tester un contrôleur
Tester un contrôleur avec MockMvc et Mockito
- Dans le dossier
/src/test/{package-de-l'application}, créez un dossier nommécontrollers - On mettra tous les fichiers de test des contrôleurs dans celui-ci.
On pourra effectuer les tests des deux situations suivantes
- La logique du contrôleur, sans gérer l'authentification
- La logique de sécurité du contrôleur
On devra donc créer deux classes différentes pour ces tests
NomControllerTestsNomControllerSecurityTests
Configuration de base d'une classe de test de contrôleur
@WebMvcTest(controllers = NomController.class)
public class NomControllerTests {
@Autowired
MockMvc mockMvc;
@MockitoBean
MonService monService;
@BeforeEach
public void setup() {
}
}
MockMvc: une classe qui permettra de simuler les appels aux routes- On injecte les services que le contrôleur utilise en leur ajoutant le décorateur
@MockitoBean @BeforeEach: une fonction qui s'exécute avant chaque test dans laquelle on peut configurer des données de base par exemple.
Écrire les tests
- Une fonction de test est toujours publique et ne retourne rien.
- On ajoute le décorateur
@Testà ces fonctions. -
Nommer une fonction de test:
nomController_nomMethode_ResultatAttendu()
-
On préparera d'avance les données qui seront retournées par les services.
- On utilise la méthode
whendeMockitopour cela.
// Extrait de ChatControllerTest
List<Chat> chats = Arrays.asList(chat);
when(chatService.findAll()).thenReturn(chats);
- On simule les appels avec
MockMvcet on vérifie le résultat de la requête avec la méthodeandExpect()ouandExpectAll()
// Extrait de ChatControllerTest
mockMvc.perform(get("/chats"))
.andExpect(status().isOk())
.andExpect(view().name("liste-chats"))
.andExpect(model().attribute("chats", chats));
Dans le cas d'un appel http dans lequel on doit fournir des données, on devra les écrire manuellement.
mockMvc.perform(post("/chats/nouveau")
.param("nom", "Picsou")
.param("couleur", "Noir et blanc")
.param("age", "3"))
Configuration pour les tests de logique
Pour tester la logique sans être embêté par l'authentification, on ajoutera le décorateur @AutoConfigureMockMvc(addFilters = false) à notre classe.
@WebMvcTest(controllers = NomController.class)
@AutoConfigureMockMvc(addFilters = false)
public class NomControllerTests {
/* ... */
}
Pour tester l'authentification et les autorisations
On pourra spécifier l'utilisateur à utiliser pour chaque test ainsi que son rôle.
On peut importer notre configuration de sécurité à la classe afin de s'assurer que c'est bien les bons filtres qui sont appliqués.
@WebMvcTest(value = ChatController.class)
@Import(SecurityConfig.class)
public class ChatControllerSecurityTests {
/* ... */
}
Pour aider à déboguer un test
On peut insérer dans la chaîne du MockMvc l'instruction andDo(print()). Ceci affichera le détail de la requête faite, et des informations reçues par cette dernière.
Exemples
import com.example.demoauth.models.Chat;
import com.example.demoauth.services.ChatService;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.when;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ChatController.class)
@AutoConfigureMockMvc(addFilters = false)
public class ChatControllerTest {
@Autowired private MockMvc mockMvc;
@MockitoBean
private ChatService chatService;
// Créer un objet Chat disponible pour tous les tests
private Chat chat;
@BeforeEach
public void setup() {
// Initialiser l'objet Chat
chat = new Chat(1, "Mitaine", "Gris", 4);
}
@Test
/**
* Méthode de test qui vérifie si la liste des chats est retournée adéquatement.
* Cette méthode teste la logique sans considérer les autorisations (addFilters = false)
*/
public void testChatController_listChats_RetourneListeDesChats() throws Exception {
List<Chat> chats = Arrays.asList(chat);
when(chatService.findAll()).thenReturn(chats);
mockMvc.perform(get("/chats"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("liste-chats"))
.andExpect(model().attribute("chats", chats));
}
}
import com.example.demoauth.models.Chat;
import com.example.demoauth.services.ChatService;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ChatController.class)
public class ChatControllerSecurityTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private ChatService chatService;
// Créer un objet Chat disponible pour tous les tests
private Chat chat;
@BeforeEach
public void setup() {
// Initialiser l'objet Chat
chat = new Chat(1, "Mitaine", "Gris", 4);
}
/**
* Méthode de test qui vérifie que la requête n'est pas autorisée pour les utilisateurs anonymes.
* L'absence de @WithMockUser simule un appel anonyme
*/
@Test
public void testChatController_listChats_Retourne401SiNonAuthentifie() throws Exception {
List<Chat> chats = Arrays.asList(chat);
when(chatService.findAll()).thenReturn(chats);
mockMvc.perform(get("/chats"))
.andDo(print())
.andExpect(status().isUnauthorized());
}
/**
* Méthode de test qui vérifie que la liste des chats est bien renvoyée pour les utilisateurs connectés
*/
@Test
@WithMockUser(username = "Bruno", roles = "USER")
public void testChatController_listChats_RetourneListeDesChatsSiAuthentifie() throws Exception {
List<Chat> chats = Arrays.asList(chat);
when(chatService.findAll()).thenReturn(chats);
mockMvc.perform(get("/chats"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("liste-chats"))
.andExpect(model().attribute("chats", chats));
}
}
Importation des méthodes
IntelliJ peut avoir de la difficulté à importer automatiquement certaines méthodes.
Les méthodes pour créer les requêtes proviennent de org.springframework.test.web.servlet.request.MockMvcRequestBuilders
Les méthodes d'assertion proviennent de org.springframework.test.web.servlet.result.MockMvcResultMatchers