Я пытаюсь написать интеграционные тесты для проекта Spring 4 MVC. В проекте я использую Spring Data JPA Repositories и Hibernate.

Ниже представлены два файла: сам тест интеграции и конфигурация для настройки моей конфигурации сохраняемости.

PersistenceTestConfiguration.java

package <project>.cms.rest.test.configuration;

import <project>.cms.rest.entity.EntityMarker;
import <project>.cms.rest.repository.RepositoryMarker;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.hibernate3.encryptor.HibernatePBEEncryptorRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackageClasses = {RepositoryMarker.class})
public class PersistenceTestConfiguration {

    @Bean
    public EntityManagerFactory entityManagerFactory() throws NamingException {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(false);
        vendorAdapter.setShowSql(true);
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setJpaVendorAdapter(vendorAdapter);
        factoryBean.setPackagesToScan(EntityMarker.class.getPackage().getName());
        factoryBean.setDataSource(dataSource());
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager() throws NamingException {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory());
        return transactionManager;
    }

    @Bean
    public HibernateExceptionTranslator exceptionTranslator() {
        return new HibernateExceptionTranslator();
    }

    @Bean
    public EmbeddedDatabase dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.H2).build();
    }
}

Проблема, с которой я столкнулся, заключается в том, что, хотя мой класс PersistenceTestConfiguration должен создавать экземпляры соответствующих репозиториев, они входят в классы обслуживания как нулевые.

Если я вручную @Mock репозиториев в тестовом классе, тогда они больше не будут нулевыми в службах, но всегда будут возвращать null, например, при вызове метода .save (entity).

Я подозреваю, что делаю что-то явно глупое, но не могу этого понять.

Будем признательны за любые указатели в правильном направлении.

Изменить 1:

Простейший возможный тест

package <project>.cms.rest.test.service;

import <project>.cms.rest.resource.PostalAddressResource;
import <project>.cms.rest.service.PostalAddressService;
import <project>.cms.rest.service.impl.PostalAddressServiceImpl;
import <project>.cms.rest.test.configuration.PersistenceTestConfiguration;
import liquibase.Liquibase;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.FileSystemResourceAccessor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.inject.Inject;
import java.sql.Connection;

@Transactional
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = { PersistenceTestConfiguration.class })
@TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public class PostalAddressServiceIntegrationTest {

    @InjectMocks
    private PostalAddressService postalAddressService = new PostalAddressServiceImpl();

    @Inject
    private EmbeddedDatabase database;

    @Before
    public void setupDatabase() throws java.sql.SQLException, LiquibaseException {
        MockitoAnnotations.initMocks(this);

        Liquibase liquibase = new Liquibase(
                "src/liquibase/liquibase-master.xml",
                new FileSystemResourceAccessor(),
                new JdbcConnection(database.getConnection())
        );

        liquibase.update("");
    }

    @After
    public void tearDown() throws java.sql.SQLException, LiquibaseException {
        Connection connection = database.getConnection();
        JdbcConnection liquibaseConnection = new JdbcConnection(connection);

        Liquibase liquibase = new Liquibase(
                "src/liquibase/liquibase-master.xml",
                new FileSystemResourceAccessor(),
                liquibaseConnection
        );

        liquibase.dropAll();
    }

    @Test
    public void testAddNewPostalAddress() {
        PostalAddressResource postalAddressResource = new PostalAddressResource();

        postalAddressResource.setBuildingNameOrNumber("1");
        postalAddressResource.setFirstLine("Test Lane");
        postalAddressResource.setSecondLine("Testville");
        postalAddressResource.setCountry("Testshire");
        postalAddressResource.setPostCode("TE5 7ER");
        postalAddressResource.setCountry("United Kingdom");

        PostalAddressResource result = postalAddressService.createPostalAddress(postalAddressResource);

        Assert.notNull(result);
    }
}

Результирующая трассировка стека

java.lang.NullPointerException
    at <project>.cms.rest.service.impl.PostalAddressServiceImpl.createPostalAddress(PostalAddressServiceImpl.java:66)
    at <project>.cms.rest.test.service.PostalAddressServiceIntegrationTest.testAddNewPostalAddress(PostalAddressServiceIntegrationTest.java:78)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
    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:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
    at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:53)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:123)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:104)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:164)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:110)
    at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:175)
    at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcessWhenForked(SurefireStarter.java:107)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:68)

Строка 66 PostalAddressServiceImpl выглядит следующим образом:

} while(repository.countByPublicId(publicId) > 0);

Изменить 2:

Создание теста, который работает напрямую с Репозиторием, а не через Сервис, работает, имея:

@Inject
private PostalAddressRepository repository;

Наверху себя ведет.

Похоже, что когда @InjectMocks должен подключать зависимости класса Service, возникает проблема, в результате чего репозиторий становится нулевым.

2
Andy Esser 27 Апр 2016 в 13:45

2 ответа

Лучший ответ

Это было решено согласно:

https://tedvinke.wordpress.com/2014/02/13/mockito-why-you-should-not-use-injectmocks-annotation-to-autowire-fields/

Изменение моих служб так, чтобы конструкторы принимали зависимости, вместо того, чтобы надеяться, что @InjectMocks выполнит работу, которую он должен делать вместо этого.

1
Andy Esser 28 Апр 2016 в 15:02

Глядя на то, что мне нужно для запуска чего-то похожего, у меня также есть такой загрузчик

@ContextConfiguration(loader = AnnotationConfigContextLoader.class,

И несколько тестовых слушателей

@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})

Интеграционное тестирование в Spring немного запутано, но в Spring Boot 1.4 было значительно упорядочено:

https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4

2
PaulNUK 27 Апр 2016 в 10:59