Как проверить весенние хранилища данных?


я хочу репозиторий (скажем,UserRepository) создан с помощью Весенних данных. Я новичок в spring-data (но не в spring), и я использую это учебник. Мой выбор технологий для работы с базой данных в JPA 2.1 и Hibernate. Проблема в том, что я не знаю, как писать модульные тесты для такого репозитория.

давайте create() метод экземпляра. Как я работаю тест-во-первых, я должен написать модульный тест для него - и вот где я натыкаюсь на три задачи:

  • во-первых, как мне ввести издевательство над EntityManager в несуществующую реализацию a UserRepository интерфейс? 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 87

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>