Skip to content

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

  • NomControllerTests
  • NomControllerSecurityTests

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 when de Mockito pour cela.
// Extrait de ChatControllerTest

List<Chat> chats = Arrays.asList(chat);
when(chatService.findAll()).thenReturn(chats);
  • On simule les appels avec MockMvc et on vérifie le résultat de la requête avec la méthode andExpect() ou andExpectAll()
// 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.

@Test
@WithMockUser(username = "User", roles = "USER")
public void nomTest () {
    /* ... */
}

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