Spring Data:服务层unit testing

在我的项目中,我在进行unit testing时遇到了麻烦。 一个问题是,只需进行集成测试就可以更快地编写并测试组件实际协同工作。 unit testing新颖的“算法”左右似乎更容易。 unit testing服务类它只是感觉错误和无用。

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

例:

@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或类似的愚蠢。 这种情况将由unit testing捕获。 “奖励 – 努力”比例看起来还不错吗?

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

  • 我正在使用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); } } 

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

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

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

总而言之,我不相信您将从使用存储库的模拟测试我已经讨论过的存储库中获得任何好处。

我要添加的另一个注释是,模拟并不是真正用于测试的实际类中。 它们的用途是为被测试的类提供所需的依赖性。

你完全正确。 这是明确的unit testing。 它永远不会失败(因此,它是无用的)我认为您需要在集成测试中使用真实数据库(例如内存中的H2 )测试真正的 JPA存储库(就像我一直这样)。

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

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

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

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

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

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

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

您可以模拟存储库并将其注入服务,这就是方法; 但是,如果您只是使用存储库的@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则不会发生这种情况; 注入发生在幕后,我们不清楚会发生什么; 这可能并不可取。

另一件事是,我不同意集成测试将涵盖unit testing将涵盖的所有情况; 它可能但并非总是如此。 甚至控制器也可以用模拟器进行unit testing; 在所有测试都要涵盖我们编写的所有代码之后,所以它们必须是细粒度的; 想象当我们遵循TTD并且我们只完成控制器和服务级别:我们如何在没有控制器unit testing的情况下继续进行?

假设我们有以下服务

 @Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeRepository employeeRepository; @Override public Employee getEmployeeByName(String name) { return employeeRepository.findByName(name); } } 

测试类:

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

要检查Service类,我们需要创建一个Service类实例并以@Beanforms提供,以便我们可以在测试类中@Autowire它。 通过使用@TestConfiguration批注实现此配置。

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

另一个有趣的事情是使用@MockBean 。 它为EmployeeRepository创建一个Mock,可以用来绕过对实际EmployeeRepository的调用:

 @Before public void setUp() { Employee alex = new Employee("alex"); Mockito.when(employeeRepository.findByName(alex.getName())) .thenReturn(alex); } 

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

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

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