单元测试实战(二)Service 的测试
为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。
本文中的测试均基于JUnit5。
单元测试实战(二)Service 的测试
概述
与Controller不同,Service的测试可以脱离Spring上下文环境。这是因为Controller测试需要覆盖从HTTP请求到handler方法的路由,即需要SpringMvc的介入;而Service则是一种比较单纯的类,可以当做简单对象来测试。
我们将使用JUnit的MockitoExtension扩展来对Service对象进行测试。待测试对象为测试类的一个属性。测试仍遵循经典三段式:given、when、then;即:假设xxx……那么当yyy时……应该会zzz。
在每个测试之前应清理/重置测试数据,即操作的业务实体。
断言应主要检查Service的行为是否符合预期。
依赖
需要的依赖与Controller测试需要的依赖相同:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-api</artifactId>
- <scope>test</scope>
- </dependency>
示例
以下是UserService的实现类UserServiceImpl。接口定义省略(从@Override注解不难推出)。
- package com.aaa.api.auth.service.impl;
-
- import com.aaa.api.auth.entity.User;
- import com.aaa.api.auth.repository.UserRepository;
- import com.aaa.api.auth.service.UserService;
- import org.springframework.stereotype.Service;
-
- import java.time.Instant;
- import java.util.List;
-
- @Service
- public class UserServiceImpl implements UserService {
-
- private final UserRepository repo;
-
- public UserServiceImpl(UserRepository repo) {
- this.repo = repo;
- }
-
- @Override
- public User findById(Long id) {
- return repo.findById(id).orElse(null);
- }
-
- @Override
- public User findByUserCode(String userCode) {
- return repo.findByUserCode(userCode).orElse(null);
- }
-
- @Override
- public User save(User user) {
- user.setGmtModified(Instant.now());
- return repo.save(user);
- }
-
- @Override
- public List<User> findAll() {
- return repo.findAll();
- }
- }
以下是对UserServiceImpl进行测试的测试类:
- package com.aaa.api.auth.service;
-
- import com.aaa.api.auth.entity.User;
- import com.aaa.api.auth.repository.UserRepository;
- import com.aaa.api.auth.service.impl.UserServiceImpl;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.junit.jupiter.api.extension.ExtendWith;
- import org.mockito.InjectMocks;
- import org.mockito.Mock;
- import org.mockito.junit.jupiter.MockitoExtension;
-
- import java.util.List;
- import java.util.Optional;
-
- import static org.assertj.core.api.Assertions.assertThat;
- import static org.junit.jupiter.api.Assertions.assertThrows;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.BDDMockito.given;
-
-
- @ExtendWith(MockitoExtension.class)
- class UserServiceTest {
-
- @Mock
- private UserRepository repo;
-
- @InjectMocks
- private UserServiceImpl svc;
-
- private final User u1 = new User();
- private final User u2 = new User();
- private final User u3 = new User();
-
- @BeforeEach
- void setUp() {
- u1.setName("张三");
- u1.setUserCode("zhangsan");
- u1.setRole(User.ADMIN);
- u1.setEmail("[email protected]");
- u1.setMobile("13600001234");
-
- u2.setName("李四");
- u2.setUserCode("lisi");
- u2.setRole(User.ADMIN);
- u2.setEmail("[email protected]");
- u2.setMobile("13800001234");
-
- u3.setName("王五");
- u3.setUserCode("wangwu");
- u3.setRole(User.USER);
- u3.setEmail("[email protected]");
- u3.setMobile("13900001234");
- }
-
- @Test
- void testFindById() {
- // given - precondition or setup
- given(repo.findById(1L)).willReturn(Optional.of(u1));
-
- // when - action or the behaviour that we are going test
- User found = svc.findById(1L);
-
- // then - verify the output
- assertThat(found).isNotNull();
- assertThat(found.getUserCode()).isEqualTo("zhangsan");
- }
-
- @Test
- void testFindByIdNegative() {
- // given - precondition or setup
- given(repo.findById(1L)).willReturn(Optional.empty());
-
- // when - action or the behaviour that we are going test
- User found = svc.findById(1L);
-
- // then - verify the output
- assertThat(found).isNull();
- }
-
- @Test
- void testFindByUserCode() {
- // given - precondition or setup
- given(repo.findByUserCode(any())).willReturn(Optional.of(u1));
-
- // when - action or the behaviour that we are going test
- User found = svc.findByUserCode("zhangsan");
-
- // then - verify the output
- assertThat(found).isNotNull();
- assertThat(found.getUserCode()).isEqualTo("zhangsan");
- }
-
- @Test
- void testFindByUserCodeNegative() {
- // given - precondition or setup
- given(repo.findByUserCode(any())).willReturn(Optional.empty());
-
- // when - action or the behaviour that we are going test
- User found = svc.findByUserCode("zhangsan");
-
- // then - verify the output
- assertThat(found).isNull();
- }
-
- @Test
- void testSave() {
- // given - precondition or setup
- given(repo.save(any(User.class))).willAnswer((invocation -> invocation.getArguments()[0]));
-
- // when - action or the behaviour that we are going test
- User saved = svc.save(u1);
-
- // then - verify the output
- assertThat(saved).isNotNull();
- assertThat(saved.getGmtModified()).isNotNull();
- }
-
- @Test
- void testSaveNegative() {
- // given - precondition or setup
- given(repo.save(any())).willThrow(new RuntimeException("Testing"));
-
- // when - action or the behaviour that we are going test
- // User saved = svc.save(u1);
-
- // then - verify the output
- assertThrows(RuntimeException.class, () -> svc.save(u1));
- }
-
- @Test
- void testFindAll() {
- // given - precondition or setup
- given(repo.findAll()).willReturn(List.of(u1, u2, u3));
-
- // when - action or the behaviour that we are going test
- List<User> found = svc.findAll();
-
- // then - verify the output
- assertThat(found).isNotNull();
- assertThat(found.size()).isEqualTo(3);
- }
- }
测试类说明:
第22行,我们使用了JUnit的MockitoExtension扩展。
第26行,我们Mock了一个UserRepository类型的对象repo,它是待测UserServiceImpl对象的依赖。由于脱离了Spring环境,所以它是个@Mock,不是@MockBean。
接着,第29行,就是待测对象svc。它有个注解@InjectMocks,意思是为该对象进行依赖注入(Mockito提供的功能);于是,repo就被注入到svc里了。
第31-33行提供了三个测试数据,并在setUp()方法中进行初始化/重置。@BeforeEach注解使得setUp()方法在每个测试之前都会执行一遍。
接下来,从56行开始,是测试方法;每个方法都遵循given - when - then三段式。
testFindById方法是测试根据id获取User对象的。它假设repository的findById(1)会返回对象u1;那么当调用svc.findById(1)时;返回的实体就应该是u1。
testFindByIdNegative方法是根据id获取User对象的负面测试。它假设找不到ID为1的User,即repository的findById(1)会返回空;那么当调用svc.findById(1)时;返回的实体应该为空。
testFindByUserCode、testFindByUserCodeNegative与testFindById、testFindByIdNegative一样,只不过查询条件换成userCode,不再赘述。
testSave方法是测试保存User对象的。它假设repository的save()方法在保存任何User对象时都会返回该对象本身;那么当调用svc.save(u1)时;返回的实体应该为u1。注意在这里我们assert了gmtModified属性,以确认UserServiceImpl.save()方法里对该属性的设置。
testSaveNegative方法是保存User对象的负面测试。它假设repository的save()方法会抛出运行时异常;那么当调用svc.save(u1)时;会接到这个异常。
testFindAll方法是测试获取所有User对象的,它假设repository的findAll()会返回对象u1、u2、u3;那么当调用svc.findAll()时;就应返回全部三个对象。
总结
Service的测试,推荐使用@ExtendWith(MockitoExtension.class),脱离Spring上下文,使用纯Mockito打桩。其它方面,理念均与Controller测试一样。
虽然Service测试的打桩器较简单,但由于业务逻辑可能都位于这一层,需要覆盖的场景多,测试用例也应该多。Service层的测试是所有层中最重要的。
原文链接:https://blog.csdn.net/ioriogami/article/details/134481160 标签:实战,given,Service,u1,单元测试,repo,User,测试,import From: https://www.cnblogs.com/sunny3158/p/17883750.html