Ich möchte ein Repository (z. B. UserRepository
), das mit Hilfe von Spring Data erstellt wurde. Ich bin neu in Spring-Daten (aber nicht in Spring) und benutze dieses Tutorial . Meine Auswahl an Technologien für den Umgang mit der Datenbank ist JPA 2.1 und Hibernate. Das Problem ist, dass ich keine Ahnung habe, wie man Unit-Tests für ein solches Repository schreibt.
Nehmen wir zum Beispiel die create()
Methode. Während ich Test-First arbeite, soll ich einen Unit-Test dafür schreiben - und dort stoße ich auf drei Probleme:
Wie füge ich zunächst einen Mock of a
EntityManager
in die nicht vorhandene Implementierung einerUserRepository
Schnittstelle ein? Spring Data würde eine Implementierung basierend auf dieser Schnittstelle generieren:public interface UserRepository extends CrudRepository<User, Long> {}
Ich weiß jedoch nicht, wie ich es zwingen soll, einen
EntityManager
Mock und andere Mocks zu verwenden. Wenn ich die Implementierung selbst geschrieben hätte, hätte ich wahrscheinlich eine Setter-MethodeEntityManager
, mit der ich meinen Mock für den Unit-Test verwenden kann. (Wie für die tatsächliche Datenbankkonnektivität, ich habe eineJpaConfiguration
Klasse, mit Anmerkungen versehen mit@Configuration
und@EnableJpaRepositories
, die programmatisch Bohnen definiert fürDataSource
,EntityManagerFactory
,EntityManager
etc. - aber Repositories sollten für das Überschreiben diese Dinge Testfreundlich und lassen sein).Zweitens sollte ich auf Interaktionen testen? Es fällt mir schwer herauszufinden, welche Methoden
EntityManager
undQuery
wie diese genannt werden sollen (ähnlichverify(entityManager).createNamedQuery(anyString()).getResultList();
), da ich nicht die Implementierung schreibe.Drittens soll ich die von Spring Data generierten Methoden zuerst einem Unit-Test unterziehen? Wie ich weiß, soll der Bibliothekscode eines Drittanbieters nicht auf Einheiten getestet werden - nur der Code, den die Entwickler selbst schreiben, soll auf Einheiten getestet werden. Aber wenn das stimmt, bringt es immer noch die erste Frage zurück in die Szene: Sagen wir, ich habe ein paar benutzerdefinierte Methoden für mein Repository, für die ich die Implementierung schreiben werde, wie füge ich meine Mocks von
EntityManager
undQuery
in das generierte Finale ein Repository?
Hinweis: Ich werde meine Repositorys sowohl mit dem Integrations- als auch mit dem Komponententest testen . Für meine Integrationstests verwende ich eine HSQL-In-Memory-Datenbank und offensichtlich keine Datenbank für Komponententests.
Und wahrscheinlich die vierte Frage: Ist es richtig, die korrekte Erstellung von Objektgraphen und das Abrufen von Objektgraphen in den Integrationstests zu testen (z. B. habe ich einen komplexen Objektgraphen mit Hibernate definiert)?
Update: Heute habe ich weiter mit der Scheininjektion experimentiert - ich habe eine statische innere Klasse erstellt, um die Scheininjektion zu ermöglichen.
@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());
}
}
Wenn ich diesen Test durchführe, erhalte ich jedoch die folgende Stapelverfolgung:
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
quelle
pom.xml
.Mit Spring Boot + Spring Data ist es ganz einfach geworden:
Die Lösung von @heez zeigt den vollständigen Kontext an. Dabei wird nur das angezeigt, was für die Funktion von JPA + Transaction erforderlich ist. Beachten Sie, dass die obige Lösung eine In-Memory-Testdatenbank aufruft, sofern diese im Klassenpfad gefunden wird.
quelle
@RunWith(SpringRuner.class)
ist jetzt schon in der enthalten@DataJpaTest
.@RunWith(SpringRunner.class
startet den Federkontext, dh es wird die Integration zwischen mehreren Einheiten überprüft. Unit Test testet eine einzelne Einheit -> einzelne Klasse. Dann schreibenMyClass sut = new MyClass();
und testen Sie das Sut-Objekt (Sut = Service im Test)Das mag etwas zu spät kommen, aber ich habe etwas für diesen Zweck geschrieben. Meine Bibliothek wird die grundlegenden Crud-Repository-Methoden für Sie verspotten und die meisten Funktionen Ihrer Abfragemethoden interpretieren. Sie müssen Funktionen für Ihre eigenen nativen Abfragen einfügen, der Rest wird jedoch für Sie erledigt.
Schau mal:
https://github.com/mmnaseri/spring-data-mock
AKTUALISIEREN
Dies ist jetzt in Maven zentral und in ziemlich gutem Zustand.
quelle
Wenn Sie Spring Boot verwenden, können Sie es einfach verwenden
@SpringBootTest
, um es in Ihr Boot zu ladenApplicationContext
(worüber Ihr Stacktrace Sie bellt). Auf diese Weise können Sie Ihre Spring-Data-Repositorys automatisch verdrahten. Stellen Sie sicher, dass Sie hinzufügen,@RunWith(SpringRunner.class)
damit die federbezogenen Anmerkungen erfasst werden:Weitere Informationen zum Testen im Frühjahrsstart finden Sie in den entsprechenden Dokumenten .
quelle
Predicate
s testen (was mein Anwendungsfall war), es funktioniert ganz gut.In der letzten Version von Spring Boot 2.1.1.RELEASE ist dies so einfach:
Vollständiger Code:
https://github.com/jrichardsz/spring-boot-templates/blob/master/003-hql-database-with-integration-test/src/test/java/test/CustomerRepositoryIntegrationTest.java
quelle
2.0.0.RELEASE
Spring Boot.Wenn Sie wirklich einen i-Test für ein Spring-Daten-Repository schreiben möchten, können Sie dies folgendermaßen tun:
Um diesem Beispiel zu folgen, müssen Sie diese Abhängigkeiten verwenden:
quelle
Ich habe das auf diese Weise gelöst -
quelle
Mit JUnit5 und
@DataJpaTest
Test sieht es so aus (Kotlin-Code):Sie können
TestEntityManager
fromorg.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
package verwenden, um den Entitätsstatus zu überprüfen.quelle