[JAVA] [SpringBoot] Comment écrire un test de contrôleur

Le test du contrôleur est spécial, donc je suis inquiet. .. Notez comment rédiger un test pour le Spring Boot Controller, qui regorge d'affaires.

Contrôleur cible

J'écrirai un test pour le contrôleur suivant.

@Controller
@RequestMapping("/")
public class DemoController {


  /**
   * index
   */
  @RequestMapping(path = "home", method = RequestMethod.GET)
  public ModelAndView index(ModelAndView mav) {
    
    //Définir une valeur pour le message
    mav.addObject("message", "hello world");
    mav.setViewName("index");
    return mav;
  }

  /**
   *Affichage de l'écran d'entrée
   */
  @RequestMapping(path = "form", method = RequestMethod.GET)
  public ModelAndView form(ModelAndView mav, Form form) {

    //Définir la valeur initiale du nom du formulaire
    form.setName("hoge");
    mav.addObject("form", form);
    mav.setViewName("demoForm");
    return mav;
  }

  /**
   *Recevez le résultat et la validation
   */
  @RequestMapping(path = "form", method = RequestMethod.POST)
  public ModelAndView formPost(ModelAndView mav, @Valid @ModelAttribute Form form,
      BindingResult result) {

    //Vérifier la validation
    if (result.hasFieldErrors()) {
      mav.addObject("errors", result.getFieldErrors());
      mav.addObject("form", form);
      mav.setViewName("demoForm");
      return mav;
    }

    //Enregistrer la valeur du formulaire
    formService.saveData(form);

    mav.setViewName("ok");
    return mav;
  }

}

En outre, la classe de formulaire utilisée pour l'envoi et la réception de formulaires est la suivante. Le nom est validé avec l'annotation @NotBlank.

Form.java


@Getter
@Setter
public class Form {

  @NotBlank(message = "Le nom est un élément obligatoire.")
  private String name;

}

Préparation

Il y a des promesses dans le test du Spring MVC Controller, alors préparez-les d'abord. Je décrirai le test détaillé dans les sections suivantes, mais je suis troublé par une erreur inattendue si cette préparation n'est pas effectuée correctement. (J'ai perdu des heures dessus.)

Tout d'abord, car il est nécessaire d'exécuter également la fonction DI de Spring sur Junit Ajoutez les annotations @Runwith (..) et @SpringBootTest à la classe de test.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DemoControllerTest {

Enregistrez la classe à tester dans le conteneur DI avec @Autowired. Préparez-vous à reproduire le comportement de Spring MVC avec MockMvcBuilders.standaloneSetup (...). L'annotation @Before est ajoutée car elle est effectuée avant chaque @Test. Après cela, cette instance mockMvc est utilisée pour générer une requête virtuelle et exécuter un test.

  private MockMvc mockMvc;

  @Autowired
  DemoController target;

  @Before
  public void setup() {
    mockMvc = MockMvcBuilders.standaloneSetup(target).build();
  }

mockMVC a été expliqué en détail sur le site here.

Le code jusqu'à présent est le suivant.

DemoController



@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DemoControllerTest {

  private MockMvc mockMvc;

  @Autowired
  DemoController target;

  @Before
  public void setup() {
    mockMvc = MockMvcBuilders.standaloneSetup(target).build();
  }

}

Méthode d'essai à domicile

Tout d'abord, écrivez un test pour la méthode home () qui dessine une vue en emballant un message dans un modèle avec la méthode GET dans / home. Les éléments à tester sont les suivants.

Regardons-les dans l'ordre.

Le code d'état HTTP de la réponse est-il correct?

Exécutez la requête à l'aide de l'exécution de mockMvc.

python


mockMvc.perform(Nom de la méthode("URL spécifiée"))

Ensuite, testez la réponse avec la méthode andExcept.

python


.andExpect(Articles de test)

Puisque cette fois nous testons le code d'état HTTP, nous utiliserons status (). Le code d'état 200 peut être testé avec status (). IsOk. Un code d'état typique peut être déterminé comme suit.

statut Méthode
200 status().isOk()
308 status().isPermanentRedirect()
404 status().isNotFound()
403 status().isForbidden()
503 status().isServiceUnavailable()

Le code jusqu'à présent est le suivant.

DemoControllerTest.java


  @Test
  public void getIndexTest() throws Exception {
    // when
    mockMvc.perform(get("/home"))
         .andExpect(status().isOk());
  }

Voulez-vous retourner la vue spécifiée?

Vérifiez si "/ home" renvoie index.html. Utilisez view (). Name () pour juger de la vue.

.andExpect(view().name(Nom du modèle))

Une fois ajouté, ce sera comme suit.

DemoControllerTest.java


  @Test
  public void getIndexTest() throws Exception {
    // when
    mockMvc.perform(get("/home"))
        .andExpect(status().isOk())
        .andExpect(view().name("index"));
  }

Le modèle contient-il les bonnes variables?

Ensuite, testez l'état du modèle pour voir si les variables utilisées dans la vue sont correctement regroupées dans le modèle. Utilisez model (). Attribute () pour tester si vous passez une variable à afficher

python


model().attribute(Nom de variable,valeur)

Cette fois, la variable appelée message est remplie avec Hello world, donc Ce sera comme suit.

python


  @Test
  public void getIndexTest() throws Exception {
    // when
    mockMvc.perform(get("/home"))
        .andExpect(status().isOk())
        .andExpect(view().name("index"))
        .andExpect(model().attribute("message", "hello world"));
  }

Il peut y avoir d'autres façons de le faire, mais une fois que le test d'index est OK pour l'instant.

Méthode Test to FormGet

La méthode form regroupe le formBean initialisé dans un modèle et affiche demoForm.html. Cependant, si vous renvoyez simplement la vue du formulaire, elle est la même qu'avant, donc testez si vous pouvez définir la valeur initiale dans le champ de nom du formulaire.

DemoController.java


    form.setName("hoge");
    mav.addObject("form", form);

La valeur initiale ("hoge") est-elle définie pour le nom du formulaire?

Le contenu de l'objet passé au modèle peut être déterminé en obtenant la valeur de retour de la requête avec mockMvc.perform (). AndReturn (). Recevoir le résultat de la requête MvcResult avec .andReturn, et obtenir la vue et le modèle avec getModelAndView à partir de là, De plus, le modèle est acquis par getModel, et la valeur de "form" est acquise par la méthode get. Notez que la valeur de retour de get () est de type object, donc castons la avec (Form). En résumé, cela ressemble à ce qui suit.

DemoControllerTest


  @Test
  public void getFormTest() throws Exception {
    // when
    MvcResult result = mockMvc.perform(get("/form"))
        .andExpect(status().isOk())
        .andExpect(view().name("demoForm"))
        .andReturn();
    //Obtenez la valeur du formulaire emballé dans le modèle ici
    Form resultForm = (Form) result.getModelAndView().getModel().get("form");

    // then 
    assertEquals(resultForm.getName(),"hoge");

  }

Test de la méthode formPost

Enfin, testez la méthode formPost. La méthode formPost reçoit la valeur d'entrée de form dans la demande de publication de demoform.html et la reçoit. Si vous effectuez une validation et qu'il n'y a pas d'erroll Il appelle FormService.saveData, enregistre le contenu du formulaire et appelle ok.html. Puisque le processus est compliqué, écrivez les éléments à tester.

Regardons chacun d'eux.

S'il y a une erreur de validation

Tout d'abord, le test lorsqu'il y a une validationError. Pour ce faire, lançons une validationError. Puisque la valeur de name est @NotBlank, une erreur se produira automatiquement si rien n'est spécifié. Ici, mettez explicitement un caractère vide dans le nom. Utilisez .param () ou .flashAttr pour entrer des valeurs pour les paramètres de la demande.

Dans le cas de param,

// form.Lors de la mise au nom
// mockMvc.perform(post("nom de l'url").param(Le nom du paramètre,valeur))
 mockMvc.perform(post("/form").param("name", "hoge"))

Lors de l'utilisation de flashAttr,

// form.Lors de la mise au nom
// mockMvc.perform(post("nom de l'url").flashAttr(Le nom du paramètre,objet))
  Form form = new Form()
  form.setName("hoge")

  mockMvc.perform((post("/form")).flashAttr("form",form))

Cette fois, nous allons tester avec flashAttr. Je teste si j'obtiens une erreur de validation et que je visualise une vue demoForm.

Le fait qu'une erreur s'est produite est ...

model().hasError()

Il est jugé par.

  @Test
  public void postFormTestInValid() throws Exception {
    // given
    Form form = new Form();
    form.setName("");

    // when
    mockMvc.perform((post("/form")).flashAttr("form",form))
        .andExpect(model().hasErrors())
        .andExpect(model().attribute("form", form))
        .andExpect(view().name("demoForm"));
  }

S'il n'y a pas d'erreur de validation

Testez ensuite si aucune erreur de validation ne se produit. L'élément que je souhaite tester ici est

Il y en a deux. Le retour ou non du code HTML spécifié est expliqué dans la section précédente, donc Décrit comment tester si vous appelez la méthode spécifiée (formService.saveData).

La première chose à faire est de se moquer du service cible. Dans springMVC, en utilisant @MockBean au lieu de @Mock, Vous pouvez vous moquer d'une classe @Autowired.

De plus, les classes avec l'annotation @MockBean sont automatiquement Il est simulé lorsque la classe @Autowired (DemoController dans ce cas) est exécutée dans la classe de test. Ajoutons donc @MockBean avant la cible @Autowired DemoController.

・
・
  private MockMvc mockMvc;

  //ajouter à
  @MockBean
  FormService mockFormService;

  @Autowired
  private DemoController target;
・
・

Ensuite, utilisez la méthode verify de Mockit pour déterminer l'état d'utilisation de l'objet Mocked. Les tests suivants qui formService.saveData sont appelés une fois avec une instance appelée form comme argument.

// verify(Nom d'objet fictif,Nombre d'utilisations).Nom de la méthode ・(argument);
verify(mockFormService, times(1)).saveData(form);

Le code de test complet est ci-dessous. Une valeur est définie dans form.name pour éviter qu'une erreur ne se produise.


  @Test
  public void postFormTestValid() throws Exception {
    // given
    Form form = new Form();
    form.setName("fuga");

    // when
    mockMvc.perform((post("/form")).flashAttr("form", form))
        .andExpect(model().hasNoErrors())
        .andExpect(model().attribute("form", form))
        .andExpect(view().name("ok"));

    // then
    verify(mockFormService, times(1)).saveData(form);
  }

résultat

Le code final est ci-dessous. La couverture de DemoController.java est désormais de 100% pour la méthode et la ligne.

DemoControllerTest.java


@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DemoControllerTest {

  private MockMvc mockMvc;

  @MockBean
  FormService mockFormService;

  @Autowired
  private DemoController target;

  @Before
  public void setup() {
    mockMvc = MockMvcBuilders.standaloneSetup(target).build();
  }

  @Test
  public void getIndexTest() throws Exception {
    // when
    mockMvc.perform(get("/home"))
        .andExpect(status().isOk())
        .andExpect(view().name("index"))
        .andExpect(model().attribute("message", "hello world"));
  }

  @Test
  public void getFormTest() throws Exception {
    // when
    MvcResult result = mockMvc.perform(get("/form"))
        .andExpect(status().isOk())
        .andExpect(view().name("demoForm"))
        .andReturn();
    Form resultForm = (Form) result.getModelAndView().getModel().get("form");

    // then
    assertEquals(resultForm.getName(), "hoge");

  }

  @Test
  public void postFormTestInValid() throws Exception {
    // given
    Form form = new Form();
    form.setName("");

    // when
    mockMvc.perform((post("/form")).flashAttr("form", form))
        .andExpect(model().hasErrors())
        .andExpect(model().attribute("form", form))
        .andExpect(view().name("demoForm"));

  }

  @Test
  public void postFormTestValid() throws Exception {
    // given
    Form form = new Form();
    form.setName("hoge");

    // when
    mockMvc.perform((post("/form")).flashAttr("form", form))
        .andExpect(model().hasNoErrors())
        .andExpect(model().attribute("form", form))
        .andExpect(view().name("ok"));

    // then
    verify(mockFormService, times(1)).saveData(form);

  }

}

Je suis toujours convaincu que ce n'est pas correct, J'espère que cela aidera les gens qui sont tout aussi inquiets de passer des tests qu'eux.

référence

https://qiita.com/NetPenguin/items/0e06779ecdd48d24a5db https://ito-u-oti.com/post-129/ http://blog.okazuki.jp/entry/2015/07/14/205627 https://terasolunaorg.github.io/guideline/5.4.1.RELEASE/ja/UnitTest/ImplementsOfUnitTest/UsageOfLibraryForTest.html

Recommended Posts

[SpringBoot] Comment écrire un test de contrôleur
Comment écrire un test unitaire pour Spring Boot 2
[Basique] Comment écrire un auto-apprentissage Dockerfile ②
[Introduction à Java] Comment écrire un programme Java
Je veux écrire un test unitaire!
Comment écrire des rails
Comment écrire docker-compose
Comment écrire Mockito
Comment écrire un fichier de migration
Rails: comment bien écrire une tâche de râteau
[Rails] Comment écrire lors de la création d'une sous-requête
Comment supprimer un contrôleur, etc. à l'aide d'une commande
JUnit 5: Comment écrire des cas de test dans enum
Comment écrire du code de test avec la certification de base
Comment exécuter l'application SpringBoot en tant que service
Comment écrire du bon code
java: Comment écrire une liste de types génériques [Note]
Comment rédiger un commentaire java
Comment laisser un commentaire
Comment écrire une recherche de comparaison de dates dans Rails
Comment écrire Junit 5 organisé
Comment écrire des graines de Rails
Pour écrire un programme orienté utilisateur (1)
Comment écrire le routage Rails
Comment écrire un mod de base dans Minecraft Forge 1.15.2
Comment insérer une vidéo
Comment créer une méthode
Comment tester une classe qui gère application.properties avec SpringBoot (requête: signalée)
Comment écrire dynamiquement des cas de test itératifs à l'aide de test / unit (Test :: Unit)
Écrivons comment créer une API avec SpringBoot + Docker à partir de 0
Comment écrire une migration du type Rails datetime au type date
Comment faire un test unitaire de Spring AOP
Étudier Java # 6 (Comment écrire des blocs)
Comment créer un conteneur Java
Comment signer Minecraft MOD
Comment créer un pilote JDBC
[Rails] Comment écrire la gestion des exceptions?
[Swift] Comment envoyer une notification
Comment écrire une déclaration de variable Java
Comment créer un écran de démarrage
Comment créer un plug-in Jenkins
Comment faire un projet Maven
Comment rédiger un code facile à comprendre [Résumé 3]
Comment créer un tableau Java
[R Spec on Rails] Comment écrire du code de test pour les débutants par les débutants
Comment exécuter un contrat avec web3j
Comment trier une liste à l'aide du comparateur