4
votes

Meilleures pratiques de test d'unité Spring Controller

Dans mon application, j'ai un contrôleur basé sur un modèle donné:

public class Controller {

@Autowired
Mapper mapper;

@Autowired
Service service;

public EntityDto create(EntityDto dto) {
    Entity entity = mapper.mapToEntity(dto);
    Entity saved = service.save(entity);
    return mapper.mapToDto(saved);
}

Quelle est une bonne approche pour tester des classes comme ça? Je vois quelques possibilités:

  1. simulez tout avec Mockito et vérifiez si un objet récupéré d'une maquette est passé à un autre
  2. faire des tests d'intégration avec un contexte Spring en cours d'exécution
  3. ignorer le test du contrôleur car il ne contient aucune logique métier

Est-ce que l'un de ceux ci-dessus est OK? Peut-être d'une autre manière?


2 commentaires

1 et 2 ensemble.


Vous avez des réponses, aucune n'est assez bonne? Veuillez préciser la question. Par exemple, votre contrôleur doit avoir des annotations comme GetMapping, RestController, etc. En ce qui concerne vos options: 1. Mock le service, mais pas le mappeur (doit avoir son propre unittest) & 2 Toujours avoir un test vérifiant un contexte Spring valide. 3. L'équipe Spring a créé MockMvc pour une raison, pour tester les contrôleurs. Je recommande de l'utiliser


7 Réponses :


1
votes

Je suppose que vous utilisez l'application Spring Boot, vous pouvez écrire votre classe de test sous le package de test comme suit:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestController {

@Autowired
Controller controller;

@Test
public void test() {
    fail("Not yet implemented");
}

@Test
public void testGroupAlert() throws EntityNotFoundException, Exception {

    Entitydto dto = new Entitydto() //Initialize your Entitydto object
    controller.create(dto);
}


3 commentaires

Mais c'est en fait une sorte de test d'intégration plutôt que d'unité ..?


Si vous voulez vraiment faire des tests unitaires, pourquoi utilisez-vous l'injection de champ?


comment est-ce lié? :)



1
votes

L'approche que j'ai vue le plus fréquemment est la suivante:

  • Le développeur qui crée le contrôleur crée des tests unitaires en utilisant se moque.
  • Une équipe QA crée des tests d'intégration avec un contexte Spring (et un outil comme concombre ).

1 commentaires

Je vois aussi cela souvent, mais IMO, cette approche est trop associée au code de production. Dans ce cas, il n'est pas possible de faire un refactoring sans casser les tests, et AFAIK c'est l'un des principaux objectifs des tests unitaires. Avec cette approche, les tests ne prendront pas en charge la refactorisation, car chaque ligne de code modifiée interrompra les tests.



0
votes

Si vous voulez faire de bons tests, le seul moyen est de faire des tests unitaires ET des tests d'intégration.

Les tests d'intégration ne sont lancés qu'en cas de besoin (par exemple: Maven peut identifier si le test est une intégration ou non).

Et les tests unitaires ne peuvent pas être remplacés par quoi que ce soit si vous voulez du code résilient.

Une bonne façon de faire est, comme vous l'avez deviné, d'utiliser des moteurs simulés. (Comme Mockito).

Le but est de vraiment contrôler la classe de toutes les manières possibles, sans l'interaction d'autres couches.

Mais ATTENTION! Cela fonctionne si vous faites cela sur TOUS les calques et toutes les classes.

Si vous travaillez sur du code existant, regardez ce qui est fait et essayez de l'utiliser et faites de votre mieux.

Et rappelez-vous qu'un bon test n'est pas celui qui couvre le plus, c'est celui qui couvre le mieux.


0 commentaires

0
votes

Votre contrôleur n'a pas de logique métier, mais il accepte et produit du JSON (ou d'autres données), qui seront utilisés par d'autres services (c'est-à-dire frontend).

C'est pourquoi il vaut la peine de vérifier un contrat d'API.

Pour cela, vous pouvez utiliser le framework Spring Could Contract .


0 commentaires

0
votes

simulez tout avec Mockito et vérifiez si un objet récupéré d'une maquette est passé à un autre

En général, vous extrayez la logique dans des méthodes qui expliquent ce qu'elles font. Si la méthode utilise le résultat d'un autre appel de méthode, alors je me moquerais de cet appel de méthode et lui ferais renvoyer une valeur fixe.

faire des tests d'intégration avec un contexte Spring en cours d'exécution

Vous devriez certainement avoir un test qui valide votre contexte Spring (vous n'en aurez peut-être besoin que d'un seul)

ignorez le test du contrôleur car il ne contient aucun journal d'activité

Votre contrôleur ne doit pas contenir de logique métier, mais plutôt déléguer à un service qui effectue un certain travail. Spring a MockMvc qui vous permet de tester un contrôleur à la fois (et de simuler d'autres beans dans le contexte de printemps). Cela offre de nombreuses opportunités pour des tests intéressants et utiles.

-

Je préfère me moquer des systèmes externes. J'aime tester le contrôleur et la logique du service. En utilisant MockMvc, je peux également valider la réponse http.

Disons que j'ai une application en trois couches: Controller -> Service -> Repository (mock this one)

Exemple-test avec MockMvc:

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 
classes = {ShoprApplication.class, TestConfig.class})
@Transactional
public class ProductControllerTest {

  @Autowired TestRestTemplate template;
  @Autowired EntityManagerFactory entityManagerFactory;
  private TestEntityManager em;

  @BeforeEach
  public void configure() {
    em = new TestEntityManager(entityManagerFactory);
  }

  @Test
  public void createProduct() {
    var response = template.postForEntity(
            "/product",
            new Product("Apples", 15.00, new Category("Fruit"), 6),
            Long.class);

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertTrue(response.getBody() != null && response.getBody().intValue() > 0L);

    var product = em.getEntityManager().find(Product.class, response.getBody());
    assertNotNull(product);
    assertEquals("Apples", product.getName());
}

Exemple sur un test qui est plus un test d'intégration (exécution d'une base de données in-mem):

 @ExtendWith(SpringExtension.class)
 @WebMvcTest(controllers = {CategoryController.class})
 public class CategoryControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private CategoryRepository repository; // mock database, but validate logic in the service-class

  @Test
  public void create_new_catgory() throws Exception {

    var category = new Category("test");
    given(repository.save(any())).willReturn(category);

    mockMvc.perform(post("/category")
            .content(toJson(new CategoryRequestDto("")))
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().is2xxSuccessful())
            .andExpect(content().json(toJson(category)));

    verify(repository, times(1)).save(any());
  }
}

0 commentaires

0
votes

1 et 2

Je pense que vous devriez tester toutes les classes avec la bibliothèque moqueuse dans toutes les couches. Même les classes les plus triviales sont bonnes à tester.

Ensuite, vous devriez avoir les tests d'intégration.

Je pense que les tests d'intégration peuvent également être exécutés tout le temps s'ils ne nécessitent aucun système externe. La seule chose peut être le temps. Je n'ai pas été dans les projets où quelques minutes d'attente pour les tests d'intégration seraient de trop.

Les bases de données en mémoire et les bibliothèques / frameworks de test d'intégration (par exemple @SpringBootTest) sont utiles pour les tests d'intégration.

Je pense que de bons tests permettent de faire beaucoup de refactoring. Les tests d'intégration ne cassent pas tellement si vous effectuez le refactoring. Les tests JUnit peuvent casser, mais je ne pense pas que ce soit un problème. Vous devriez toujours avoir un code et des tests aussi simples et propres que possible.


0 commentaires

0
votes
  1. Déplacez la plupart de la logique du domaine vers le modèle (vous voudrez peut-être consulter un livre sur DDD)
  2. Couvrir le modèle de domaine avec des tests unitaires. Ainsi, la plupart des tests restent au niveau de l'unité et n'ont pas besoin de se moquer.
  3. Pour chaque élément de fonctionnalité, n'écrivez que quelques tests de haut niveau - ils vérifieront que la sérialisation, l'AOP, le mappage d'URL sont correctement effectués et que tout fonctionne de la tête aux pieds. Vous pouvez utiliser MockMvc pour cela.

Vous vous retrouvez avec une Pyramide de test - beaucoup de tests unitaires rapides, pas autant de tests de haut niveau qui vérifient tout ensemble, pas de frameworks moqueurs.


0 commentaires