网站开发程序员,建设网站要多长时间,云网站功能,亳州做网站为鼓励单元测试#xff0c;特分门别类示例各种组件的测试代码并进行解说#xff0c;供开发人员参考。
本文中的测试均基于JUnit5。
单元测试实战#xff08;一#xff09;Controller 的测试
单元测试实战#xff08;二#xff09;Service 的测试
单元测试实战#x…为鼓励单元测试特分门别类示例各种组件的测试代码并进行解说供开发人员参考。
本文中的测试均基于JUnit5。
单元测试实战一Controller 的测试
单元测试实战二Service 的测试
单元测试实战三JPA 的测试
单元测试实战四MyBatis-Plus 的测试
单元测试实战五普通类的测试
单元测试实战六其它
概述
与Controller不同Service的测试可以脱离Spring上下文环境。这是因为Controller测试需要覆盖从HTTP请求到handler方法的路由即需要SpringMvc的介入而Service则是一种比较单纯的类可以当做简单对象来测试。
我们将使用JUnit的MockitoExtension扩展来对Service对象进行测试。待测试对象为测试类的一个属性。测试仍遵循经典三段式given、when、then即假设xxx……那么当yyy时……应该会zzz。
在每个测试之前应清理/重置测试数据即操作的业务实体。
断言应主要检查Service的行为是否符合预期。
依赖
需要的依赖与Controller测试需要的依赖相同
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope
/dependency
dependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter-api/artifactIdscopetest/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;}Overridepublic User findById(Long id) {return repo.findById(id).orElse(null);}Overridepublic User findByUserCode(String userCode) {return repo.findByUserCode(userCode).orElse(null);}Overridepublic User save(User user) {user.setGmtModified(Instant.now());return repo.save(user);}Overridepublic ListUser 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 {Mockprivate UserRepository repo;InjectMocksprivate UserServiceImpl svc;private final User u1 new User();private final User u2 new User();private final User u3 new User();BeforeEachvoid setUp() {u1.setName(张三);u1.setUserCode(zhangsan);u1.setRole(User.ADMIN);u1.setEmail(zhangsanaaa.net.cn);u1.setMobile(13600001234);u2.setName(李四);u2.setUserCode(lisi);u2.setRole(User.ADMIN);u2.setEmail(lisiaaa.net.cn);u2.setMobile(13800001234);u3.setName(王五);u3.setUserCode(wangwu);u3.setRole(User.USER);u3.setEmail(wangwuaaa.net.cn);u3.setMobile(13900001234);}Testvoid testFindById() {// given - precondition or setupgiven(repo.findById(1L)).willReturn(Optional.of(u1));// when - action or the behaviour that we are going testUser found svc.findById(1L);// then - verify the outputassertThat(found).isNotNull();assertThat(found.getUserCode()).isEqualTo(zhangsan);}Testvoid testFindByIdNegative() {// given - precondition or setupgiven(repo.findById(1L)).willReturn(Optional.empty());// when - action or the behaviour that we are going testUser found svc.findById(1L);// then - verify the outputassertThat(found).isNull();}Testvoid testFindByUserCode() {// given - precondition or setupgiven(repo.findByUserCode(any())).willReturn(Optional.of(u1));// when - action or the behaviour that we are going testUser found svc.findByUserCode(zhangsan);// then - verify the outputassertThat(found).isNotNull();assertThat(found.getUserCode()).isEqualTo(zhangsan);}Testvoid testFindByUserCodeNegative() {// given - precondition or setupgiven(repo.findByUserCode(any())).willReturn(Optional.empty());// when - action or the behaviour that we are going testUser found svc.findByUserCode(zhangsan);// then - verify the outputassertThat(found).isNull();}Testvoid testSave() {// given - precondition or setupgiven(repo.save(any(User.class))).willAnswer((invocation - invocation.getArguments()[0]));// when - action or the behaviour that we are going testUser saved svc.save(u1);// then - verify the outputassertThat(saved).isNotNull();assertThat(saved.getGmtModified()).isNotNull();}Testvoid testSaveNegative() {// given - precondition or setupgiven(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 outputassertThrows(RuntimeException.class, () - svc.save(u1));}Testvoid testFindAll() {// given - precondition or setupgiven(repo.findAll()).willReturn(List.of(u1, u2, u3));// when - action or the behaviour that we are going testListUser found svc.findAll();// then - verify the outputassertThat(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层的测试是所有层中最重要的。