Spring Data:服务层单元测试

时间:2013-03-15 16:20:15

标签: java spring unit-testing spring-data

在我的项目中,我在进行单元测试时遇到了麻烦。一个问题是,只进行集成测试要快得多,并且还要测试组件实际上是否一起工作。单元测试新颖的“算法”左右似乎要容易得多。单元测试服务类它只是感觉错误和无用。

我正在使用mockito来模拟spring数据存储库(以及数据库访问)。问题是如果我告诉模拟的存储库在方法调用getById上返回实体A,它显然会返回它,服务也将返回它。是的,该服务会做一些额外的东西,但是非常小的东西,比如加载延迟集合(来自hibernate)。显然我在单元测试中没有任何惰性集合(代理)。

示例:

@Test
public void testGetById() {
    System.out.println("getById");
    TestCompound expResult = new TestCompound(id, "Test Compound", "9999-99-9", null, null, null);

    TestCompoundRepository mockedRepository = mock(TestCompoundRepository.class);
    when(mockedRepository.findOne(id)).thenReturn(expResult);

    ReflectionTestUtils.setField(testCompoundService, "testCompoundRepository",
            mockedRepository, TestCompoundRepository.class);

    TestCompound result = testCompoundService.getById(id);
    assertEquals(expResult, result);
}
万岁,其余的都成功了。多么惊喜!不是真的没有。

有人可以向我解释我做错了什么吗?或者这样一个测试的重点是什么?我的意思是我告诉返回expResult然后它返回。哇。多么惊喜!感觉就像我在测试mockito是否有效而不是我的服务。

编辑:

我看到的唯一好处是,如果有些是愚蠢的错误,就像留下一个不需要的行,将返回值设置为null或类似的愚蠢。这种情况将由单元测试捕获。 “奖励 - 努力”比例看起来还不错吗?

6 个答案:

答案 0 :(得分:13)

问题可能有点旧,但我会给出答案以防万一有人偶然发现。

  • 我使用的是Mockito和JUnit。
  • AccountRepository是一个扩展JPARepository的普通spring数据存储库。
  • 帐户是一个普通的JPA实体。

要测试您的服务并模拟Spring Data存储库,您需要以下内容。

package foo.bar.service.impl;

import foo.bar.data.entity.Account;
import foo.bar.data.repository.AccountRepository;
import foo.bar.service.AccountService;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class AccountServiceImplTest {

    @Mock
    private static AccountRepository accountRepository;

    @InjectMocks
    private static AccountService accountService = new AccountServiceImpl();

    private Account account;

    @Test
    public void testFindAccount() {

        Integer accountId = new Integer(1);

        account = new Account();
        account.setId(accountId);
        account.setName("Account name");
        account.setCode("Accont code");
        account.setDescription("Account description");

        Mockito.when(accountRepository.findOne(accountId)).thenReturn(account);

        Account retrivedAccount = accountService.findAccount(accountId);

        Assert.assertEquals(account, retrivedAccount);

    }

}

答案 1 :(得分:8)

我喜欢测试Spring Data存储库的原因之一是测试我是否正确定义了JPA映射。我不使用模拟框架进行这些测试,我使用Spring Test框架实际引导容器,允许我将实际存储库自动装入Junit测试,以便我可以对它执行测试。

我同意你的想法,嘲笑存储库是没用的。由于您使用Spring,我建议利用Spring Test框架对您的存储库执行真正的测试,这些测试可以针对嵌入式数据库(如H2)以更多基于单元测试的方式或您的实际数据库实现(如Oracle或MySql)执行。进行更多的集成测试。 (针对开发数据库的副本执行这些测试)这些测试将揭示JPA映射中的谬误以及数据库中不正确的级联设置等其他项目。

这是我对GitHub进行的一项测试的example。注意框架实际上如何将存储库自动装入测试中。存储库还包含一个如何配置Spring Test框架的示例,我在此blog post中也演示了该框架。

总之,我认为您不会从使用存储库模拟测试存储库中获得任何好处。

我想补充的另一个注意事项是,模拟实际上并不打算用于实际的测试类。它们的用途是为被测试的类提供必要的依赖。

答案 2 :(得分:1)

你完全正确。这是明确的单元测试。它永远不会失败(因此,它没用)我认为您需要在集成测试中使用真实数据库(H2在内存中测试真实 JPA存储库例子)(我一如既往)。

最好测试您的服务(他们的接口)。如果在一段时间后你将更改你的存储空间(例如Mongo) - 你将能够使用你的服务测试来确保所有工作都像以前一样。

一段时间后,您会惊讶地发现有多少与DB \ JPA相关的问题(约束,乐观锁,延迟加载,重复ID,一些休眠问题等)。

另外,尝试通过测试开发 - 而不仅仅是在实现后编写测试。相反,在创建服务中的新方法之前 - 为它创建测试,实现服务方法,只有在实际应用程序中重新检查它之后。至少它比服务器开始测试要快得多。

所以,不要创建测试以拥有大量测试。了解他们如何帮助您。

使用mocks作为存储库并不是一个好主意。测试您的服务如何与Hibernate \ JPA \ Database一起使用。问题的大部分位于 beetwen layers

答案 3 :(得分:1)

您可以使用此库:https://github.com/agileapes/spring-data-mock

这将为您模拟您的存储库,同时允许您为任何方法和本机查询方法实现自定义功能。

答案 4 :(得分:0)

您可以模拟存储库,然后将其注入服务中;但是,如果仅使用@Mock个存储库实例化服务,则最好将存储库定义为服务中的private final个字段,并使用所有存储库的构造函数。这样,如果将另一个存储库添加到服务,则测试将失败,因此必须更改它。

想象一下这项服务:

class OrderService {
    private final UserRepository userRepos;

    public OrderService(UserRepository userRepos) {
        this.userRepos = userRepos;
    }

    ...

}

这个测试:

class OrderServiceTests {
    @Mock
    private UserRepository userRepos;

    private OrderService service;

    private OrderServiceTests() {
        this.service = new OrderService(this.userRepos);
    }
}

现在,如果我们向服务添加另一个依赖项:

class OrderService {
    private final UserRepository userRepos;
    private final AddressRepository addRepos;

    public OrderService(UserRepository userRepos, AddressRepository addRepos) {
        this.userRepos = userRepos;
        this.addRepos = addRepos;

    ...

}

由于构造函数已更改,因此先前的测试将失败。如果您使用@InjectMocks,则不会发生;注射发生在幕后,我们不清楚发生了什么。这可能是不可取的。

另一件事是,我不同意集成测试将涵盖单元测试将涵盖的所有情况;可能但并非总是如此。甚至控制器也可以使用模拟进行单元测试;毕竟,所有测试旨在涵盖我们编写的所有代码,因此必须细粒度;想像一下,当我们遵循TTD并仅完成控制器和服务级别时:我们如何在没有控制器单元测试的情况下进行?

答案 5 :(得分:0)

假设我们具有下面的 Service

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {

       @Bean
       public EmployeeService employeeService() {
           return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

// write test cases here
}

Test 类:

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

要检查Service类,我们需要创建一个Service类的实例并以 @Bean 的形式使用,以便我们可以在测试类中 @Autowire 。此配置是通过使用 @TestConfiguration 注释实现的。

在组件扫描期间,我们可能会发现仅为特定测试而创建的组件或配置会在各地意外获取。为了防止这种情况的发生,Spring Boot提供了 @TestConfiguration 批注,该批注可用于 src / test / java 中的类,以指示不应通过扫描来拾取它们。 / p>

这里另一个有趣的事情是使用 @MockBean 。它为EmployeeRepository创建一个模拟,可用于绕过对实际EmployeeRepository的调用:

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);

    assertThat(found.getName())isEqualTo(name);
}

设置完成后,我们可以轻松测试服务,例如:

{{1}}

有关更深入的知识检查: https://www.baeldung.com/spring-boot-testing