如何测试Spring Data存储库?

时间:2014-05-02 19:54:48

标签: unit-testing jpa spring-data-jpa spring-data

我想要一个在Spring Data的帮助下创建的存储库(比如UserRepository)。我是spring-data(但不是spring)的新手,我使用这个tutorial。我选择处理数据库的技术是JPA 2.1和Hibernate。问题是我对如何为这样的存储库编写单元测试一无所知。

让我们以create()方法为例。当我正在测试时,我应该为它编写单元测试 - 这就是我遇到三个问题的地方:

  • 首先,如何将EntityManager的模拟注入到UserRepository接口的不存在实现中? Spring Data将基于此接口生成实现:

    public interface UserRepository extends CrudRepository<User, Long> {}
    

    但是,我不知道如何强制它使用EntityManager模拟和其他模拟 - 如果我自己编写了实现,我可能会有EntityManager的setter方法,允许我使用我的模拟进行单元测试。 (至于实际的数据库连接,我有一个JpaConfiguration类,注释了@Configuration@EnableJpaRepositories,它以编程方式定义了DataSourceEntityManagerFactory,{{ 1}}等等 - 但是存储库应该是测试友好的并允许覆盖这些东西。)

  • 其次,我应该测试互动吗?我很难弄清楚应该调用EntityManagerEntityManager的方法(类似于Query),因为我不是在编写实施

  • 第三,我是否应该首先对Spring-Data生成的方法进行单元测试?据我所知,第三方库代码不应该进行单元测试 - 只有开发人员自己编写的代码应该进行单元测试。但如果这是真的,它仍然会将第一个问题带回现场:比方说,我的存储库有几个自定义方法,我将为其编写实现,如何注入我的模拟{{ 1}}和verify(entityManager).createNamedQuery(anyString()).getResultList();进入最终生成的存储库?

注意:我将使用两者集成和单元测试来测试我的存储库。对于我的集成测试,我使用的是HSQL内存数据库,显然我没有使用数据库进行单元测试。

可能是第四个问题,在集成测试中测试正确的对象图创建和对象图检索是否正确(比方说,我有一个用Hibernate定义的复杂对象图)?

更新:今天我继续尝试模拟注入 - 我创建了一个静态内部类来允许模拟注入。

EntityManager

但是,运行此测试会给我以下堆栈跟踪:

Query

10 个答案:

答案 0 :(得分:94)

TL;博士

简而言之 - 由于一个简单的原因,无法合理地对Spring Data JPA存储库进行单元测试:模拟我们调用以引导的JPA API的所有部分是很麻烦的。库。无论如何,单元测试在这里没有多大意义,因为您通常不会自己编写任何实现代码(请参阅下面关于自定义实现的段落),以便集成测试是最合理的方法。

详细

我们进行了大量的前期验证和设置,以确保您只能引导没有无效派生查询等的应用。

  • 我们为派生查询创建和缓存CriteriaQuery个实例,以确保查询方法不包含任何拼写错误。这需要使用Criteria API以及meta.model。
  • 我们通过要求EntityManager为这些实例创建Query实例来验证手动定义的查询(这有效地触发了查询语法验证)。
  • 我们检查Metamodel有关为准备新检查等而处理的域类型的元数据。

您可能在手写存储库中推迟的所有内容可能导致应用程序在运行时中断(由于查询无效等)。

如果您考虑一下,那么您没有为您的存储库编写代码,因此无需编写任何单元测试。根本没有必要,因为你可以依靠我们的测试基础来捕获基本的错误(如果你碰巧碰到一个,请随意提出ticket)。但是,确实需要集成测试来测试持久层的两个方面,因为它们是与您的域相关的方面:

  • 实体映射
  • 查询语义(无论如何都会在每次引导尝试时验证语法)。

集成测试

这通常通过使用内存数据库和通常通过测试上下文框架(正如您已经做过)引导Spring ApplicationContext的测试用例来完成,预先填充数据库(通过插入对象实例) EntityManager或repo,或通过普通的SQL文件),然后执行查询方法来验证它们的结果。

测试自定义实现

存储库的自定义实现部分是written in a way,他们不必了解Spring Data JPA。它们是普通的春豆,注入EntityManager。你当然可以试图嘲笑与它的互动,但说实话,单元测试JPA对我们来说并不是一个太愉快的体验,而且它适用于很多间接(EntityManager - &gt ; CriteriaBuilderCriteriaQuery等等。这样你最终会得到嘲讽返回嘲笑等等。

答案 1 :(得分:32)

使用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 + Transaction工作所需的内容。 请注意,上面的解决方案将显示内存测试数据库,因为可以在类路径中找到一个。

答案 2 :(得分:20)

这可能有点太晚了,但我已经为此目的写了一些东西。我的库将为您模拟基本的crud存储库方法,并解释查询方法的大多数功能。 您必须为自己的本机查询注入功能,但其余的都是为您完成的。

看看:

https://github.com/mmnaseri/spring-data-mock

<强>更新

现在位于Maven中心,状态非常好。

答案 3 :(得分:13)

如果您正在使用Spring Boot,则只需使用@SpringBootTest加载ApplicationContext(这就是您的堆栈跟踪对您的咆哮)。这允许您在spring-data存储库中自动装配。请务必添加@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"));
  }
}

您可以在docs中了解有关春季启动测试的更多信息。

答案 4 :(得分:4)

我通过这种方式解决了这个问题-

    @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;
        .......
        .......
}

答案 5 :(得分:4)

当您真的想为spring数据存储库编写i-test时,可以这样做:

@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>

答案 6 :(得分:3)

在最新版本的spring boot 2.1.1.RELEASE 中,它很简单:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SampleApplication.class)
public class CustomerRepositoryIntegrationTest {

    @Autowired
    CustomerRepository repository;

    @Test
    public void myTest() throws Exception {

        Customer customer = new Customer();
        customer.setId(100l);
        customer.setFirstName("John");
        customer.setLastName("Wick");

        repository.save(customer);

        List<?> queryResult = repository.findByLastName("Wick");

        assertFalse(queryResult.isEmpty());
        assertNotNull(queryResult.get(0));
    }
}

完整代码:

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

答案 7 :(得分:2)

使用JUnit5和@DataJpaTest测试看起来像(kotlin代码):

@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来验证实体状态。

答案 8 :(得分:0)

springboot 2.4.5


import javax.persistence.EntityManager;
import javax.persistence.ParameterMode;
import javax.persistence.PersistenceContext;
import javax.persistence.StoredProcedureQuery;

@Repository
public class MyRepositoryImpl implements MyRepository {
    @Autowired
    @PersistenceContext(unitName = "MY_JPA_UNIT")
    private EntityManager entityManager;
    
    
    @Transactional("MY_TRANSACTION_MANAGER")
    @Override
    public MyEntity getSomething(Long id) {
    
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery(
                "MyStoredProcedure", MyEntity.class);
        query.registerStoredProcedureParameter("id", Long.class, ParameterMode.IN);
        query.setParameter("id", id);

        query.execute();
        
        @SuppressWarnings("unchecked")
        MyEntity myEntity = (MyEntity) query.getResultList().stream().findFirst().orElse(null);
        return myEntity;
    }
}

import org.junit.jupiter.api.*;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;

import javax.persistence.EntityManager;
import javax.persistence.StoredProcedureQuery;
import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

@RunWith(MockitoJUnitRunner.Silent.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyRepositoryTest {

    @InjectMocks
    MyRepositoryImpl myRepository;

    @Mock
    private EntityManager entityManager;

    @Mock
    private StoredProcedureQuery storedProcedureQuery;
    
    @BeforeAll
    public void init() {
        MockitoAnnotations.openMocks(this);
        Mockito.when(entityManager.createStoredProcedureQuery(Mockito.any(), Mockito.any(Class.class)))
                .thenReturn(storedProcedureQuery);
    }

    @AfterAll
    public void tearDown() {
        // something
    }
    
    @Test
    void testMethod() throws Exception {
        Mockito.when(storedProcedureQuery.getResultList()).thenReturn(List.of(myEntityMock));

        MyEntity resultMyEntityList = myRepository.getSomething(1l);

        assertThat(resultMyEntityList,
                allOf(hasProperty("id", org.hamcrest.Matchers.is("1"))
                . . .
        );
    }
}

答案 9 :(得分:0)

在 2021 年通过一个新的初始化 springboot 2.5.1 项目,我会这样做:

...
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;


@ExtendWith(MockitoExtension.class)
@DataJpaTest
public class RepositoryTest {

    @Autowired
    MyRepository repo;

    @Test
    public void myTest() throws Exception {
        repo.save(new MyRepoEntity());
        ...
    }
}