Как проверить весенние хранилища данных?
я хочу репозиторий (скажем,UserRepository
) создан с помощью Весенних данных. Я новичок в spring-data (но не в spring), и я использую это учебник. Мой выбор технологий для работы с базой данных в JPA 2.1 и Hibernate. Проблема в том, что я не знаю, как писать модульные тесты для такого репозитория.
давайте create()
метод экземпляра. Как я работаю тест-во-первых, я должен написать модульный тест для него - и вот где я натыкаюсь на три задачи:
-
во-первых, как мне ввести издевательство над
EntityManager
в несуществующую реализацию aUserRepository
интерфейс? Spring Data будет генерировать реализацию на основе этого интерфейса:public interface UserRepository extends CrudRepository<User, Long> {}
однако, я не знаю, как заставить его использовать
EntityManager
mock и другие насмешки - если бы я сам написал реализацию, у меня, вероятно, был бы метод сеттера дляEntityManager
, что позволяет мне использовать мой макет для модульного теста. (Что касается фактическое подключение к базе данных, у меня естьJpaConfiguration
класс, с аннотацией@Configuration
и@EnableJpaRepositories
, который программно определяет бобы дляDataSource
,EntityManagerFactory
,EntityManager
etc. - но репозитории должны быть удобными для тестирования и позволять переопределять эти вещи). во-вторых, я должен проверить взаимодействие? Мне трудно понять, какие методы
EntityManager
иQuery
должны называться (сродни этомуverify(entityManager).createNamedQuery(anyString()).getResultList();
), так как это не я пишу реализация.в-третьих, должен ли я в первую очередь тестировать методы Spring-Data-generated? Как я знаю, сторонний код библиотеки не должен быть проверен на модуль-только код, который разработчики пишут сами, должен быть проверен на модуль. Но если это правда, это все равно возвращает первый вопрос к сцене: скажем, у меня есть несколько пользовательских методов для моего репозитория, для которых я буду писать реализацию, как я могу вводить свои насмешки
EntityManager
иQuery
в финал, созданный репозиторий?
Примечание: я буду тест-драйв мои репозитории с помощью и интеграционные и модульные тесты. Для моих интеграционных тестов я использую базу данных HSQL в памяти, и я, очевидно, не использую базу данных для модульных тестов.
и, вероятно, четвертый вопрос, Правильно ли проверять правильное создание графа объектов и извлечение графа объектов в интеграционных тестах (скажем, I есть ли сложный граф объектов, определенный с помощью Hibernate)?
Update: сегодня я продолжил экспериментировать с макетной инъекцией-я создал статический внутренний класс, чтобы обеспечить макетную инъекцию.
@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());
}
}
однако выполнение этого теста дает мне следующий stacktrace:
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.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.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access0(ParentRunner.java:53)
at org.junit.runners.ParentRunner.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.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
7 ответов:
tl; dr
чтобы сделать его коротким - нет никакого способа модульного тестирования JPA-репозиториев Spring Data разумно по простой причине: это способ громоздко издеваться над всеми частями API JPA, которые мы вызываем для начальной загрузки репозиториев. Модульные тесты не имеют большого смысла здесь в любом случае, так как вы обычно не пишете код реализации самостоятельно (см. ниже параграф о пользовательских реализациях), так что интеграционное тестирование является наиболее разумным подход.
подробности
мы делаем довольно много предварительной проверки и настройки, чтобы убедиться, что вы можете только загрузки приложение, которое имеет неверный полученных запросов и т. д.
- мы создаем и кэш
CriteriaQuery
экземпляры для производных запросов, чтобы убедиться, что методы запроса не содержат опечаток. Это требует работы с API критериев, а также мета.модель.- мы проверяем вручную определенные запросы, задавая
EntityManager
создатьQuery
экземпляр для тех (который эффективно запускает проверку синтаксиса запроса).- мы осматриваем
Metamodel
для метаданных о типах доменов, обрабатываемых для подготовки is-новых проверок и т. д.все вещи, которые вы, вероятно, отложите в рукописном репозитории, которые могут привести к разрыву приложения во время выполнения (из-за недопустимых запросов и т. д.).
если вы думаете об этом, нет никакого кода, который вы пишете для своих репозиториев, поэтому нет необходимости писать любой unitтесты. Там просто нет необходимости, так как вы можете положиться на нашу тестовую базу, чтобы поймать основные ошибки (если вы все еще сталкиваетесь с одним, не стесняйтесь поднимать авиабилет). Тем не менее, определенно необходимо, чтобы интеграционные тесты проверяли два аспекта вашего уровня сохраняемости, поскольку они являются аспектами, связанными с вашим доменом:
- сущность сопоставлений
- семантика запросов (синтаксис проверяется при каждой попытке начальной загрузки в любом случае.)
интеграционные тесты
обычно это делается с помощью базы данных в памяти и тестовых случаев, которые загружают Spring
ApplicationContext
обычно через тестовый контекст framework (как вы уже делаете), предварительно заполнить базу данных (путем вставки экземпляров объектов черезEntityManager
или РЕПО, или через простой файл SQL), а затем выполните методы запроса, чтобы проверить их результат.тестирование пользовательских реализаций
Custom реализация части репозитория являются написано так что они не должны знать о весенних данных JPA. Это простые весенние бобы, которые gen an
EntityManager
вводят. Вы, конечно, можете попытаться высмеять взаимодействия с ним, но, честно говоря, модульное тестирование JPA не было слишком приятным опытом для нас, а также оно работает с довольно большим количеством косвенных действий (EntityManager
->CriteriaBuilder
,CriteriaQuery
etc.) так что вы в конечном итоге с насмешками возвращения насмешек и так далее.
С Spring Boot + Spring Data это стало довольно легко:
@RunWith(SpringRunner.class) @DataJpaTest public class MyRepositoryTest { @Autowired MyRepository subject; @Test public void myTest() throws Exception { subject.save(new MyEntity()); } }
решение @heez поднимает полный контекст, это только поднимает то, что необходимо для работы транзакции JPA+. Обратите внимание, что приведенное выше решение вызовет тестовую базу данных в памяти, учитывая, что ее можно найти на пути к классам.
Это может прийти немного слишком поздно, но я написал кое-что для этой самой цели. Моя библиотека будет издеваться над основными методами репозитория crud для вас, а также интерпретировать большинство функциональных возможностей ваших методов запроса. Вам придется вводить функциональные возможности для ваших собственных собственных запросов, но все остальное сделано за вас.
принять смотри:
https://github.com/mmnaseri/spring-data-mock
обновление
Это теперь в Maven central и в довольно хорошей форме.
если вы используете Spring Boot, вы можете просто использовать
@SpringBootTest
для загрузки в вашApplicationContext
(это то, что ваш stacktrace лает на вас). Это позволяет вам автоматически подключаться к вашим хранилищам данных spring. Будьте уверены, чтобы добавить@RunWith(SpringRunner.class)
Итак, весенние аннотации подобраны:@RunWith(SpringRunner.class) @SpringBootTest public class OrphanManagementTest { @Autowired private UserRepository userRepository; @Test public void saveTest() { User user = new User("Tom"); userRepository.save(user); Assert.assertNotNull(userRepository.findOne("Tom")); } }
вы можете прочитать больше о тестировании в spring boot в их docs.
С JUnit5 и
@DataJpaTest
тест будет выглядеть так (код Котлина):@DataJpaTest @ExtendWith(value = [SpringExtension::class]) class ActivityJpaTest { @Autowired lateinit var entityManager: TestEntityManager @Autowired lateinit var myEntityRepository: MyEntityRepository @Test fun shouldSaveEntity() { // when val savedEntity = myEntityRepository.save(MyEntity(1, "test") // then Assertions.assertNotNull(entityManager.find(MyEntity::class.java, savedEntity.id)) } }
вы могли бы использовать
TestEntityManager
Сorg.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
пакет для проверки состояния объекта.
Я решил это, используя этот способ -
@RunWith(SpringRunner.class) @EnableJpaRepositories(basePackages={"com.path.repositories"}) @EntityScan(basePackages={"com.model"}) @TestPropertySource("classpath:application.properties") @ContextConfiguration(classes = {ApiTestConfig.class,SaveActionsServiceImpl.class}) public class SaveCriticalProcedureTest { @Autowired private SaveActionsService saveActionsService; ....... ....... }
когда вы действительно хотите написать i-тест для репозитория данных spring, вы можете сделать это следующим образом:
@RunWith(SpringRunner.class) @DataJpaTest @EnableJpaRepositories(basePackageClasses = WebBookingRepository.class) @EntityScan(basePackageClasses = WebBooking.class) public class WebBookingRepositoryIntegrationTest { @Autowired private WebBookingRepository repository; @Test public void testSaveAndFindAll() { WebBooking webBooking = new WebBooking(); webBooking.setUuid("some uuid"); webBooking.setItems(Arrays.asList(new WebBookingItem())); repository.save(webBooking); Iterable<WebBooking> findAll = repository.findAll(); assertThat(findAll).hasSize(1); webBooking.setId(1L); assertThat(findAll).containsOnly(webBooking); } }
чтобы следовать этому примеру, вы должны использовать следующие зависимости:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.197</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.9.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>