Je veux un référentiel (disons, UserRepository
) créé avec l'aide de Spring Data. Je suis nouveau sur spring-data (mais pas sur spring) et j'utilise ce tutoriel . Mon choix de technologies pour traiter la base de données est JPA 2.1 et Hibernate. Le problème est que je ne sais pas comment écrire des tests unitaires pour un tel référentiel.
Prenons la create()
méthode par exemple. Comme je travaille d'abord sur le test, je suis censé écrire un test unitaire pour cela - et c'est là que je rencontre trois problèmes:
Tout d'abord, comment puis-je injecter une maquette d'un
EntityManager
dans l'implémentation non existante d'uneUserRepository
interface? Spring Data générerait une implémentation basée sur cette interface:public interface UserRepository extends CrudRepository<User, Long> {}
Cependant, je ne sais pas comment le forcer à utiliser un
EntityManager
simulacre et d'autres simulacres - si j'avais écrit l'implémentation moi-même, j'aurais probablement une méthode setter pourEntityManager
, me permettant d'utiliser mon simulacre pour le test unitaire. (En ce qui concerne la connectivité de base de données réelle, j'ai uneJpaConfiguration
classe, annotée avec@Configuration
et@EnableJpaRepositories
qui définit pour les haricots programmeDataSource
,EntityManagerFactory
,EntityManager
etc. - mais les dépôts doivent être faciles à tester et permettre la modification de ces choses).Deuxièmement, dois-je tester les interactions? Il m'est difficile de comprendre quelles méthodes de
EntityManager
etQuery
sont censées être appelées (comme celaverify(entityManager).createNamedQuery(anyString()).getResultList();
), car ce n'est pas moi qui écris l'implémentation.Troisièmement, suis-je censé tester les méthodes générées par Spring-Data en premier lieu? Comme je le sais, le code de la bibliothèque tierce n'est pas censé être testé unitaire - seul le code que les développeurs écrivent eux-mêmes est censé être testé unitaire. Mais si c'est vrai, cela ramène toujours la première question à la scène: disons, j'ai quelques méthodes personnalisées pour mon référentiel, pour lesquelles j'écrirai l'implémentation, comment injecter mes simulations de
EntityManager
etQuery
dans le final, généré dépôt?
Remarque: Je vais tester mes référentiels en utilisant à la fois l'intégration et les tests unitaires. Pour mes tests d'intégration, j'utilise une base de données HSQL en mémoire, et je n'utilise évidemment pas de base de données pour les tests unitaires.
Et probablement la quatrième question, est-il correct de tester la création correcte du graphe d'objet et la récupération du graphe d'objet dans les tests d'intégration (par exemple, j'ai un graphe d'objet complexe défini avec Hibernate)?
Mise à jour: aujourd'hui, j'ai continué à expérimenter avec l'injection simulée - j'ai créé une classe interne statique pour permettre l'injection fictive.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
@TransactionConfiguration(defaultRollback = true)
public class UserRepositoryTest {
@Configuration
@EnableJpaRepositories(basePackages = "com.anything.repository")
static class TestConfiguration {
@Bean
public EntityManagerFactory entityManagerFactory() {
return mock(EntityManagerFactory.class);
}
@Bean
public EntityManager entityManager() {
EntityManager entityManagerMock = mock(EntityManager.class);
//when(entityManagerMock.getMetamodel()).thenReturn(mock(Metamodel.class));
when(entityManagerMock.getMetamodel()).thenReturn(mock(MetamodelImpl.class));
return entityManagerMock;
}
@Bean
public PlatformTransactionManager transactionManager() {
return mock(JpaTransactionManager.class);
}
}
@Autowired
private UserRepository userRepository;
@Autowired
private EntityManager entityManager;
@Test
public void shouldSaveUser() {
User user = new UserBuilder().build();
userRepository.save(user);
verify(entityManager.createNamedQuery(anyString()).executeUpdate());
}
}
Cependant, exécuter ce test me donne le stacktrace suivant:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:101)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:319)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:212)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'entityManager' threw exception; nested exception is java.lang.IllegalArgumentException: JPA Metamodel must not be null!
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1493)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1197)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:684)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:121)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:100)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:250)
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContextInternal(CacheAwareContextLoaderDelegate.java:64)
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:91)
... 28 more
Caused by: org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'entityManager' threw exception; nested exception is java.lang.IllegalArgumentException: JPA Metamodel must not be null!
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:108)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:62)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1489)
... 44 more
la source
pom.xml
.Avec Spring Boot + Spring Data, c'est devenu assez simple:
La solution de @heez évoque le contexte complet, cela ne fait apparaître que ce qui est nécessaire pour que JPA + Transaction fonctionne. Notez que la solution ci-dessus affichera une base de données de test en mémoire étant donné que l'on peut en trouver une sur le chemin de classe.
la source
@RunWith(SpringRuner.class)
est maintenant déjà inclus dans le@DataJpaTest
.@RunWith(SpringRunner.class
démarre le contexte de ressort, ce qui signifie qu'il vérifie l'intégration entre plusieurs unités. Le test unitaire teste une seule unité -> une seule classe. Ensuite, vous écrivezMyClass sut = new MyClass();
et testez l'objet sut (sut = service sous test)Cela peut arriver un peu trop tard, mais j'ai écrit quelque chose dans ce but précis. Ma bibliothèque se moquera des méthodes de base du référentiel crud pour vous et interprétera la plupart des fonctionnalités de vos méthodes de requête. Vous devrez injecter des fonctionnalités pour vos propres requêtes natives, mais le reste est fait pour vous.
Regarde:
https://github.com/mmnaseri/spring-data-mock
METTRE À JOUR
C'est maintenant dans le centre de Maven et en assez bon état.
la source
Si vous utilisez Spring Boot, vous pouvez simplement utiliser
@SpringBootTest
pour charger dans votreApplicationContext
(c'est ce à quoi votre stacktrace vous aboie). Cela vous permet de se connecter automatiquement à vos référentiels de données de printemps. Assurez-vous d'ajouter@RunWith(SpringRunner.class)
afin que les annotations spécifiques au ressort soient prises en compte:Vous pouvez en savoir plus sur les tests dans Spring Boot dans leur documentation .
la source
Predicate
s (ce qui était mon cas d'utilisation), cela fonctionne assez bien.Dans la dernière version de spring boot 2.1.1.RELEASE , c'est simple comme:
Code complet:
https://github.com/jrichardsz/spring-boot-templates/blob/master/003-hql-database-with-integration-test/src/test/java/test/CustomerRepositoryIntegrationTest.java
la source
2.0.0.RELEASE
du printemps Boot.Lorsque vous voulez vraiment écrire un i-test pour un référentiel de données Spring, vous pouvez le faire comme ceci:
Pour suivre cet exemple, vous devez utiliser ces dépendances:
la source
J'ai résolu cela en utilisant de cette façon -
la source
Avec JUnit5 et le
@DataJpaTest
test ressemblera à (code kotlin):Vous pouvez utiliser
TestEntityManager
fromorg.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
package afin de valider l'état de l'entité.la source