Saya ingin repositori (katakanlah, UserRepository
) dibuat dengan bantuan Spring Data. Saya baru mengenal spring-data (tetapi tidak untuk spring) dan saya menggunakan tutorial ini . Pilihan teknologi saya untuk menangani database adalah JPA 2.1 dan Hibernate. Masalahnya adalah saya tidak tahu bagaimana menulis unit test untuk repositori tersebut.
Mari kita ambil create()
metode misalnya. Saat saya mengerjakan tes pertama, saya seharusnya menulis unit test untuk itu - dan di situlah saya menabrak tiga masalah:
Pertama, bagaimana cara menyuntikkan tiruan
EntityManager
ke dalam implementasiUserRepository
antarmuka yang tidak ada? Spring Data akan menghasilkan implementasi berdasarkan antarmuka ini:public interface UserRepository extends CrudRepository<User, Long> {}
Namun, saya tidak tahu bagaimana memaksanya untuk menggunakan
EntityManager
tiruan dan tiruan lainnya - jika saya telah menulis implementasinya sendiri, saya mungkin akan memiliki metode setter untukEntityManager
, memungkinkan saya untuk menggunakan tiruan saya untuk pengujian unit. (Adapun konektivitas database sebenarnya, saya memilikiJpaConfiguration
kelas, dijelaskan dengan@Configuration
dan@EnableJpaRepositories
, yang pemrograman mendefinisikan kacang untukDataSource
,EntityManagerFactory
,EntityManager
dll - tapi repositori harus ramah-test dan memungkinkan untuk mengesampingkan hal-hal ini).Kedua, haruskah saya menguji interaksi? Sulit bagi saya untuk mencari tahu metode apa
EntityManager
danQuery
seharusnya dipanggil (seperti ituverify(entityManager).createNamedQuery(anyString()).getResultList();
), karena bukan saya yang menulis implementasinya.Ketiga, apakah saya seharusnya menguji unit metode Spring-Data yang dihasilkan di tempat pertama? Seperti yang saya tahu, kode perpustakaan pihak ketiga tidak seharusnya diuji unit - hanya kode yang ditulis sendiri oleh pengembang yang seharusnya diuji unit. Tetapi jika itu benar, itu masih membawa pertanyaan pertama kembali ke tempat kejadian: katakanlah, saya punya beberapa metode khusus untuk repositori saya, di mana saya akan menulis implementasi, bagaimana saya menyuntikkan ejekan saya
EntityManager
danQuery
ke final, dihasilkan gudang?
Catatan: Saya akan uji-mengemudi repositori saya menggunakan kedua integrasi dan unit test. Untuk pengujian integrasi, saya menggunakan basis data dalam-memori HSQL, dan saya jelas tidak menggunakan basis data untuk pengujian unit.
Dan mungkin pertanyaan keempat, apakah benar untuk menguji pembuatan grafik objek yang benar dan pengambilan grafik objek dalam tes integrasi (katakanlah, saya memiliki grafik objek kompleks yang didefinisikan dengan Hibernate)?
Pembaruan: hari ini saya terus bereksperimen dengan injeksi tiruan - Saya membuat kelas dalam statis untuk memungkinkan injeksi tiruan.
@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());
}
}
Namun, menjalankan tes ini memberi saya stacktrace berikut:
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
sumber
pom.xml
.Dengan Spring Boot + Spring Data menjadi sangat mudah:
Solusi oleh @heez memunculkan konteks penuh, ini hanya memunculkan apa yang diperlukan agar Transaksi JPA + berfungsi. Perhatikan bahwa solusi di atas akan memunculkan database uji memori mengingat seseorang dapat ditemukan di classpath.
sumber
@RunWith(SpringRuner.class)
sekarang sudah termasuk dalam@DataJpaTest
.@RunWith(SpringRunner.class
memulai konteks pegas yang berarti memeriksa integrasi antara beberapa unit. Uji unit menguji satu unit -> kelas tunggal. Kemudian Anda menulisMyClass sut = new MyClass();
dan menguji objek sut (sut = layanan yang diuji)Ini mungkin agak terlambat, tetapi saya telah menulis sesuatu untuk tujuan ini. Perpustakaan saya akan mengejek metode repositori crud dasar untuk Anda serta menafsirkan sebagian besar fungsi metode kueri Anda. Anda harus menyuntikkan fungsionalitas untuk kueri asli Anda sendiri, tetapi sisanya dilakukan untuk Anda.
Lihatlah:
https://github.com/mmnaseri/spring-data-mock
MEMPERBARUI
Ini sekarang di pusat Maven dan dalam kondisi yang cukup baik.
sumber
Jika Anda menggunakan Spring Boot, Anda dapat menggunakan
@SpringBootTest
untuk memuat diApplicationContext
(yang merupakan apa yang Anda ketahui tentang stacktrace). Ini memungkinkan Anda untuk autowire di repositori data pegas Anda. Pastikan untuk menambahkan@RunWith(SpringRunner.class)
agar anotasi khusus pegas diambil:Anda dapat membaca lebih lanjut tentang pengujian pada booting musim semi di dokumen mereka .
sumber
Predicate
s (yang merupakan kasus penggunaan saya) itu berfungsi dengan baik.Dalam versi terakhir dari booting musim semi 2.1.1.RELEASE , sederhana seperti:
Kode lengkap:
https://github.com/jrichardsz/spring-boot-templates/blob/master/003-hql-database-with-integration-test/src/test/java/test/CustomerRepositoryIntegrationTest.java
sumber
2.0.0.RELEASE
Spring Boot.Ketika Anda benar-benar ingin menulis i-test untuk repositori data pegas Anda dapat melakukannya seperti ini:
Untuk mengikuti contoh ini Anda harus menggunakan dependensi ini:
sumber
Saya memecahkan ini dengan menggunakan cara ini -
sumber
Dengan JUnit5 dan
@DataJpaTest
tes akan terlihat seperti (kode kotlin):Anda dapat menggunakan
TestEntityManager
dariorg.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
paket untuk memvalidasi status entitas.sumber