web-dev-qa-db-fra.com

Spring Data: tests unitaires de la couche de service

Dans mon projet, j'ai du mal à faire des tests unitaires. Un problème est que le simple fait de réaliser un test d'intégration est beaucoup plus rapide à écrire et permet également de vérifier que les composants fonctionnent réellement ensemble. Le test unitaire de nouveaux "algorithmes" semble beaucoup plus facile. Les classes de service de tests unitaires ne donnent pas l'impression d'être utiles et inutiles. 

J'utilise mockito pour simuler le référentiel de données de printemps (et donc l'accès aux bases de données). Le problème, c’est que si je dis au référentiel simulé de renvoyer l’entité A lors de l’appel de méthode getById, il le renverra évidemment et le service le renverra aussi. Oui, le service propose des options supplémentaires, mais des tâches très mineures, telles que charger des collections paresseuses (à partir de Hibernate). Évidemment, je n'ai pas de collections paresseuses (mandataires) dans un test unitaire.

Exemple:

@Test
public void testGetById() {
    System.out.println("getById");
    TestCompound expResult = new TestCompound(id, "Test Compound", "9999-99-9", null, null, null);

    TestCompoundRepository mockedRepository = mock(TestCompoundRepository.class);
    when(mockedRepository.findOne(id)).thenReturn(expResult);

    ReflectionTestUtils.setField(testCompoundService, "testCompoundRepository",
            mockedRepository, TestCompoundRepository.class);

    TestCompound result = testCompoundService.getById(id);
    assertEquals(expResult, result);
}

hourra, le reste réussit. Quelle surprise! Non, pas vraiment. 

Quelqu'un peut-il m'expliquer ce que je fais mal? Ou alors quel est l'intérêt d'un tel test? Je veux dire que je dis de retourner expResult et ensuite, il est renvoyé. Sensationnel. Quelle surprise! J'ai l'impression que je teste si Mockito fonctionne et non mon service.

MODIFIER:

Le seul avantage que je vois, si certaines erreurs stupides se produisent, est comme de laisser une ligne non désirée qui définit la valeur de retour sur null ou un objet similaire stupide. De tels cas seraient compris dans le test unitaire. Pourtant, le ratio "récompense-effort" semble mauvais?

20
beginner_

La question est peut-être un peu ancienne, mais je vais vous donner une réponse au cas où quelqu'un trébuche.

  • J'utilise Mockito et JUnit.
  • AccountRepository est un référentiel de données simple qui étend JPARepository.
  • Le compte est une entité JPA simple.

Pour tester vos services et simuler des référentiels Spring Data, vous avez besoin de quelque chose comme ci-dessous.

package foo.bar.service.impl;

import foo.bar.data.entity.Account;
import foo.bar.data.repository.AccountRepository;
import foo.bar.service.AccountService;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class AccountServiceImplTest {

    @Mock
    private static AccountRepository accountRepository;

    @InjectMocks
    private static AccountService accountService = new AccountServiceImpl();

    private Account account;

    @Test
    public void testFindAccount() {

        Integer accountId = new Integer(1);

        account = new Account();
        account.setId(accountId);
        account.setName("Account name");
        account.setCode("Accont code");
        account.setDescription("Account description");

        Mockito.when(accountRepository.findOne(accountId)).thenReturn(account);

        Account retrivedAccount = accountService.findAccount(accountId);

        Assert.assertEquals(account, retrivedAccount);

    }

}
11
Desorder

L'une des raisons pour lesquelles j'aime tester mes référentiels Spring Data est de vérifier que j'ai correctement défini mes mappages JPA. Je n'utilise pas de framework moqueur pour ces tests, j'utilise le framework Spring Test qui amorce le conteneur, ce qui me permet d'autowire le référentiel réel dans le test Junit afin que je puisse exécuter des tests sur celui-ci.

Je suis d'accord avec vos pensées pour dire que se moquer du référentiel est plutôt inutile. Depuis que vous utilisez Spring, je vous suggère de tirer parti de la structure Spring Test pour effectuer de vrais tests sur vos référentiels, qui peuvent être exécutés sur une base de données intégrée telle que H2 de manière plus basée sur les tests unitaires ou sur votre implémentation réelle de base de données telle que Oracle ou MySql effectuer plus d'un test d'intégration. (Exécutez-les sur une copie d'une base de données de développement). Ces tests révèlent des erreurs dans vos mappages JPA et d'autres éléments tels qu'une configuration incorrecte des cascades dans la base de données.

Voici un exemple de l'un de mes tests sur GitHub. Notez que la structure envoie automatiquement le référentiel au test. Le référentiel contient également un exemple de configuration du framework Spring Test, que j'ai également démontré dans cet article blog post .

En conclusion, je ne pense pas que vous bénéficierez des avantages de tester un référentiel dont j'ai discuté en utilisant une maquette du référentiel.

Une remarque supplémentaire que je voulais ajouter, c'est que les simulacres ne sont pas vraiment destinés à être utilisés dans la classe à l'essai. Leur utilisation sert à fournir les dépendances requises à une classe sous test.

8
Kevin Bowersox

Tu as tout à fait raison. C'est un test unitaire clair. Et cela ne manquera jamais (donc, c'est inutile). Je pense que vous avez besoin, lors du test d'intégration, de tester le référentielrealJPA avecrealdatabase (H2 en mémoire par exemple) (comme je le fais toujours). 

Et il vaut mieux tester vos services (leurs interfaces). Si, après un certain temps, vous modifiez votre stockage (Mongo par exemple), vous pourrez utiliser vos tests de service pour vous assurer que tout fonctionne comme avant.

Après un certain temps, vous serez surpris du nombre de problèmes liés à DB\JPA (contraintes, verrous optimistes, chargement différé, chargement en double, identifiants en double, problèmes d'hibernation, etc.).

Essayez également de développer via des tests - pas seulement d'écrire test après implémentation. Au lieu de cela, avant la création d’une nouvelle méthode dans le service - créez un test pour elle, implémentez la méthode de service et seulement après une nouvelle vérification dans une application réelle. Au moins, le démarrage du test est beaucoup plus rapide qu’un serveur.

Donc, ne créez pas de tests pour en avoir beaucoup. Trouvez comment ils peuvent vous aider.

L'utilisation de simulacres pour les dépôts n'est pas une bonne idée. Testez le fonctionnement de vos services avec Hibernate\JPA\Database. La plupart des problèmes sont localisés entre deux couches .

1
Michail Nikolaev

Vous pouvez utiliser cette bibliothèque: https://github.com/agileapes/spring-data-mock

Cela simulera votre référentiel, tout en vous permettant d'implémenter des fonctionnalités personnalisées pour toutes les méthodes, ainsi que vos méthodes de requête natives.

1
Milad Naseri

En supposant que nous ayons la classe Service ci-dessous

@Service 
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

Test classe:

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {

       @Bean
       public EmployeeService employeeService() {
           return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

// write test cases here
}

Pour vérifier la classe Service, nous devons avoir une instance de la classe Service créée et disponible sous la forme @Bean afin de pouvoir @Autowire dans notre classe de test. Cette configuration est réalisée à l'aide de l'annotation @TestConfiguration.

Lors de l'analyse de composants, il est possible que des composants ou des configurations créées uniquement pour des tests spécifiques soient accidentellement détectés partout. Pour éviter cela, Spring Boot fournit une annotation @TestConfiguration qui peut être utilisée sur les classes de src/test/Java pour indiquer qu'elles ne doivent pas être récupérées par l'analyse.

Une autre chose intéressante ici est l'utilisation de @MockBean. Il crée une maquette pour le EmployeeRepository qui peut être utilisé pour ignorer l'appel au EmployeeRepository réel:

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

Après la configuration, nous pouvons facilement tester notre service comme:

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);

    assertThat(found.getName())isEqualTo(name);
}

Pour un contrôle de connaissances plus approfondi: https://www.baeldung.com/spring-boot-testing

0
Petros Stergioulas

Vous pouvez vous moquer du référentiel et l'injecter dans le service, c'est le chemin; mais, si vous instanciez simplement le service avec @Mock de référentiels, il serait préférable de définir les référentiels comme des champs private final dans le service et d'utiliser un constructeur de tous les référentiels. Ainsi, si vous ajoutez un autre référentiel au service, le test échouera et vous devrez le modifier, ce qui est le but.

Imaginez ce service:

class OrderService {
    private final UserRepository userRepos;

    public OrderService(UserRepository userRepos) {
        this.userRepos = userRepos;
    }

    ...

}

Et ce test:

class OrderServiceTests {
    @Mock
    private UserRepository userRepos;

    private OrderService service;

    private OrderServiceTests() {
        this.service = new OrderService(this.userRepos);
    }
}

Maintenant, si nous ajoutons une autre dépendance au service:

class OrderService {
    private final UserRepository userRepos;
    private final AddressRepository addRepos;

    public OrderService(UserRepository userRepos, AddressRepository addRepos) {
        this.userRepos = userRepos;
        this.addRepos = addRepos;

    ...

}

Le test précédent échouera car le constructeur a changé. Si vous utilisez @InjectMocks, cela ne se produira pas. l'injection se fait derrière le rideau et on ne sait pas ce qui se passe; cela peut ne pas être souhaitable.

Une autre chose est, je ne suis pas d'accord que le test d'intégration couvrira tous les cas que les tests unitaires couvriront; cela peut mais pas toujours le cas. Même le contrôleur peut être testé avec des modèles; Après tous les tests sont censés couvrir tout le code que nous avons écrit, ils doivent donc être affinés; imaginons que lorsque nous suivons TTD et que nous ne complétions que le niveau de contrôleur et de services: comment procéder sans tester l’unité de contrôle?

0
WesternGun