Нельзя юнит-тестировать Spring Data JPA @Query, правда? - PullRequest
0 голосов
/ 01 января 2019

Прежде всего, цель.Что такое юнит-тест?Модульный тест - это тест, который тестирует наименьшую часть функциональности, в отличие от интеграционных тестов, которые тестируют гораздо больше, например:

  • тест, который порождает производство ApplicationContext любым способом или в любой форме, НЕ является модульным тестом;
  • тест, который касается или даже знает о классе Application (отмечен @SpringBootApplication), НЕ является модульным тестом;
  • тест, который загружает что-то из src/main/resources, НЕ является модульным тестом
  • тест, который загружает внешнюю конфигурацию с сервера Spring Cloud Config, определенно не является модульным тестом;
  • модульный тест репозитория не должен запускать (или даже знать о) проблемы сети, mvc или безопасности.

Итак, как написать модульный тест для репозитория Spring Data JPA?(или настолько популярный и любимый фреймворк не поддерживает чистый модульный тест для такой вещи?)

Мой проект: Spring Cloud (сервис Cloud Counfig, сервис Security OAuth2, eureka, zuul, аутентификация, авторизация и т. д.)

Давайте попробуем протестировать простейший репозиторий:

public interface StudentRepository extends CrudRepository<Student, Integer> {
    Optional<Student> findByStudentCode(Integer studentCode);
    Optional<Student> findTopByOrderByStudentCodeDesc();

    @Query(value = "SELECT COUNT(*) = 0 FROM t_student WHERE regexp_replace(LOWER(student_name), '\\s', '', 'g') = regexp_replace(LOWER(:suspect), '\\s', '', 'g')", nativeQuery = true)
    boolean isStudentNameSpeciallyUnique(@Param("suspect") String studentName);
}

Субъект учащегося будет иметь: идентификатор, код (натуральный идентификатор), имя, возраст.Ничего особенного.А вот и тест.Нам нужен SUT (наш репозиторий) и менеджер сущностей для предварительного заполнения SUT.Итак:

@RunWith(SpringRunner.class)
@DataJpaTest // <-- loads full-blown production app context and fails!
public class StudentRepositoryTest {

    @Autowired
    TestEntityManager manager;

    @Autowired
    StudentRepository repository;

    @Test
    public void findByStudentCode_whenNoSuch_shouldReturnEmptyOptional() {
        final int invalidStudentCode = 321;
        Optional<Student> student = repository.findByStudentCode(invalidStudentCode);
        assertFalse(student.isPresent());
    }
}

Попытка запустить его приводит к:

...
2019-01-01 18:32:10.750  INFO 15868 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2d72f75e: startup date [Tue Jan 01 18:32:10 EET 2019]; root of context hierarchy
2019-01-01 18:32:11.294  INFO 15868 --- [           main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2019-01-01 18:32:11.421  INFO 15868 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'configurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$9ed1a748] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.6.RELEASE)

2019-01-01 18:32:30.694  INFO 15868 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:8888\
...

@DataJpaTest ищет иерархию пакетов вверх и загружает производственное приложение:

package org.givespring.atry.university.studentservice;
@EnableResourceServer
@EnableJpaAuditing
@SpringBootApplication(scanBasePackages = "org.givespring.atry.university") // to discover MicroserviceProperties and custom usercontext stuff
public class StudentServiceApp {
    public static void main(String[] args) {
        SpringApplication.run(StudentServiceApp.class, args);
    }
}

В результате выдается ошибка:

2019-01-01 18:32:32.789 ERROR 15868 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: Unable to retrieve @EnableAutoConfiguration base packages
    at org.springframework.boot.autoconfigure.AutoConfigurationPackages.get(AutoConfigurationPackages.java:76) ~[spring-boot-autoconfigure-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport.getBasePackages(AbstractRepositoryConfigurationSourceSupport.java:79) ~[spring-boot-autoconfigure-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport$1.getBasePackages(AbstractRepositoryConfigurationSourceSupport.java:73) ~[spring-boot-autoconfigure-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.data.repository.config.RepositoryConfigurationSourceSupport.lambda$getCandidates$2(RepositoryConfigurationSourceSupport.java:77) ~[spring-data-commons-2.0.11.RELEASE.jar:2.0.11.RELEASE]
    at org.springframework.data.util.LazyStreamable.stream(LazyStreamable.java:50) ~[spring-data-commons-2.0.11.RELEASE.jar:2.0.11.RELEASE]
    at org.springframework.data.util.LazyStreamable.iterator(LazyStreamable.java:41) ~[spring-data-commons-2.0.11.RELEASE.jar:2.0.11.RELEASE]
    at org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport.getRepositoryConfigurations(RepositoryConfigurationExtensionSupport.java:87) ~[spring-data-commons-2.0.11.RELEASE.jar:2.0.11.RELEASE]
    at org.springframework.data.repository.config.RepositoryConfigurationDelegate.registerRepositoriesIn(RepositoryConfigurationDelegate.java:126) ~[spring-data-commons-2.0.11.RELEASE.jar:2.0.11.RELEASE]
    at org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport.registerBeanDefinitions(AbstractRepositoryConfigurationSourceSupport.java:60) ~[spring-boot-autoconfigure-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:358) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_121]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:357) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:146) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:118) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:328) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:692) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:530) ~[spring-context-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:386) ~[spring-boot-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127) [spring-boot-test-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:44) [spring-boot-test-autoconfigure-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) [spring-test-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137) [junit-4.12.jar:4.12]
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) [junit-rt.jar:na]
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) [junit-rt.jar:na]
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) [junit-rt.jar:na]
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) [junit-rt.jar:na]

2019-01-01 18:32:32.792  INFO 15868 --- [           main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6a2eea2a: startup date [Tue Jan 01 18:32:32 EET 2019]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@2d72f75e

1 Ответ

0 голосов
/ 02 января 2019

В ярлыке модульный тест - это быстрый тест небольшого устройства.Что такое блок, зависит от руководства разработчика или архитектуры.Это может быть любой небольшой тестируемый фрагмент кода, такой как метод или класс .

Также, если вы думаете об этомнет никакого кода, который вы пишете для своих репозиториев.Вы зависите от Spring Platform.Если вы обнаружите какую-либо ошибку, не стесняйтесь поднять тикет

Так что не нужно писать какие-либо модульные тесты для методов репозитория Spring Data JPA.

Если вам нужно написать модульный тест для репозитория Spring Data JPA, я полагаю, что ваша проблема связана с методами: save, delete, findOne, findAll и т. Д. В этом случае, как вы можете видеть, это не small , поэтому требуется интеграционный тест .

Этот репозиторий содержит пример интеграционного теста с методами репозитория Spring jpa:

https://github.com/jrichardsz/spring-boot-templates/blob/master/003-hql-database-with-integration-test/src/test/java/test/CustomerRepositoryIntegrationTest.java

Ссылки:

...