单元测试实战(二)Service 的测试

这篇具有很好参考价值的文章主要介绍了单元测试实战(二)Service 的测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。

本文中的测试均基于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测试需要的依赖相同:

<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("zhangsan@aaa.net.cn");
        u1.setMobile("13600001234");
 
        u2.setName("李四");
        u2.setUserCode("lisi");
        u2.setRole(User.ADMIN);
        u2.setEmail("lisi@aaa.net.cn");
        u2.setMobile("13800001234");
 
        u3.setName("王五");
        u3.setUserCode("wangwu");
        u3.setRole(User.USER);
        u3.setEmail("wangwu@aaa.net.cn");
        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://www.toymoban.com/news/detail-761895.html

到了这里,关于单元测试实战(二)Service 的测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 单元测试实战(二)Service 的测试

    为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。 本文中的测试均基于JUnit5。 单元测试实战(一)Controller 的测试 单元测试实战(二)Service 的测试 单元测试实战(三)JPA 的测试     单元测试实战(四)MyBatis-Plus 的测试 单元测试实战(

    2024年02月04日
    浏览(23)
  • Service层代码单元测试以及单元测试如何Mock

    接着上一篇文章:单元测试入门篇,本篇文章作为单元测试的进阶篇,主要介绍如何对Springboot Service层代码做单元测试,以及单元测试中涉及外调服务时,如何通过Mock完成测试。 现在项目都流行前后端代码分离,后端使用springboot框架,在service层编写接口代码实现逻辑。假设

    2023年04月08日
    浏览(36)
  • springboot对service方法进行单元测试

    1. 在pom.xml文件添加依赖 2. service类 这里为了简化,没有将接口和实现单独定义。 3. 测试类 @SpringBootTest注解会将springboot程序完整的运行起来。还可以写成@SpringBootTest(classes = OrderApplication.class),即指定启动类。  4. 执行测试 5. 除了启动整个应用以外,还可以只加载需要的组件

    2024年02月11日
    浏览(34)
  • Spring Boot中的service层

    spring Boot中的service层是业务逻辑层,负责处理业务需求,封装业务方法,调用dao层的数据操作1。service层一般是一个接口和一个实现类,用@Service注解标注实现类2。service层的接口可以在controller层中调用,实现数据的传递和处理。 一个service层的示例代码如下: 首先,需要定义

    2024年02月12日
    浏览(33)
  • 【Spring Boot】service 业务层简单使用

    在controller同级目录下创建service 在controller里出入service

    2024年04月12日
    浏览(25)
  • Mock单元测试----对Controller层进行单独测试,不调用Service层

    前言:根据相关需求,需要对编写的代码进行逻辑检测以及功能的完整性,从而开始了单元测试之路。在编写的中间段时,突然被 不经过Service层直接测试Controller层 这个要求难住了。在我看来,单元测试除了Junit还是Junit,属实是学艺不精,之后接触了Mock,才发现Mock太牛逼了

    2024年02月05日
    浏览(27)
  • SpringBoot——Service单元测试(包含mybatis、mapper、私有方法等)

      在写单元测试时,免不了遇到私有方法、数据库等一些操作,此时就需要一些mock处理。

    2024年02月13日
    浏览(27)
  • 基于Junit4+Mockito+PowerMock实现Controller+Service的单元测试

    一 导入的依赖 二 依赖版本 三 controller测试示例代码       controller         controllerTest         测试结果:覆盖率100%         带异常的Controller         带异常提示的ControllerTest         测试结果,覆盖率100%   三 service测试示例代码         service         serviceTest    

    2024年02月14日
    浏览(34)
  • spring boot,DAO层、ENTITY层、SERVICE层、CONTROLLER层之间的关系

    主要用于 定义与数据库对象应的属性,提供get/set方法 ,tostring方法,有参无参构造函数。 DAO层 首先会创建Dao接口 , 接着就可以在配置文件中定义该接口的实现类 ;接着就可以在模块中调用Dao的接口进行数据业务的处理,而不用关注此接口的具体实现类是哪一个类,Dao层的数

    2024年04月10日
    浏览(27)
  • Spring Boot项目中的Controller、Service、Mapper和Entity层的作用与联系

    在Spring Boot项目中,常见的四个层次是Controller层、Service层、Mapper层和Entity层。它们各自承担着不同的职责,但彼此之间存在着紧密的联系。本文将详细介绍这四个层次的作用与联系,并提供相关实例来说明它们之间的关系。 Controller层是Spring Boot应用程序的入口点,用于处理

    2024年02月10日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包