mockito+junit完成单元测试

一:单元测试的特点

  • 配合断言使用(可以杜绝System.out)
  • 可以重复执行
  • 不依赖环境
  • 不会对数据产生影响
  • spring的上下文环境不是必须的
  • 一般都需要配合mock类框架来实现

二:常用的mock类框架

mockito

官网:Mockito framework site

另外现在像powermock和JMockito现在都不常用了;

三:Mockito的单独使用

(1)mock对象和spy对象

|------|--------|--------------|------|----------|
| | 方法插桩 | 方法不插桩 | 作用对象 | 最佳实践 |
| mock | 执行插桩逻辑 | 返回mock对象的默认值 | 类、接口 | 被测试类或其依赖 |
| spy | 执行插桩逻辑 | 调用真实方法 | 类、接口 | 被测试类 |

(2)初始化mock/spy对象的方式

|--------|----------------------------------------------|----------------------------|---------------------------------------------|
| | 方法一 | 方法二 | 方法三 |
| junit4 | @RunWith(MockitoJUnitRunner.class)+@Mock等注解 | Mockito.mock(X.class)等静态方法 | MockitoAnnotations.openMocks(this)+@Mock等注解 |
| junit5 | @ExtendWith(MockitoExtension.class)+@Mock等注解 | Mockito.mock(X.class)等静态方法 | MockitoAnnotations.openMocks(this)+@Mock等注解 |

四:具体实例操作

Controller层:

java 复制代码
@Slf4j
@RestController
@Validated
public class UserController{
    @Resource
    private UserService userService;

    @GetMapping("/selectById")
    public UserVO selectById(@NotNull Long userId){
        return userService.selectById(userId);
    }

    @PostMapping("/add")
    public String add(@RequestBody @Validated UserAddReq addReq){
        userService.add(addReq.getUsername(),addReq.getPhone(),addReq.getfeatures());
        return ok;
    }
}

方法一:@ExtendWith(MockitoExtension.class)+@Mock等注解

java 复制代码
@ExtendWith(MockitoExtension.class)
public class InitMockSpyMethod1{
    @mock
    private UserService mockUserService;
    @Spy
    private UserService spyUserService;

    @Test
    public void test1(){
        //true     判断某对象是不是mock对象
        System.out.println("Mockito.mockingDetails(mockUserService).isMock() = " + Mockito.mockingDetails(mockUserService).isMock());

        //false     判断某对象是不是apy对象
        System.out.println("Mockito.mockingDetails(mockUserService).isMock() = " + Mockito.mockingDetails(mockUserService).isMock());

        //true     判断某对象是不是spy对象
        System.out.println("Mockito.mockingDetails(spyUserService).isSpy() = " + Mockito.mockingDetails(spyUserService).isSpy());

        //true     判断某对象是不是spy对象,因为spy是一种特殊的mock(spy对象是另一种不同类型的mock对象)
        System.out.println("Mockito.mockingDetails(spyUserService).isMock() = " + Mockito.mockingDetail(spyUserService).isMock());
    }
}

spy对象是一种特殊的mock对象

方法二:Mockito.mock(X.class)等静态方法

java 复制代码
public class InitMockSpyMethod1{
    private UserService mockUserService;
    private UserService spyUserService;

    @BeforeEach
    public void init(){
        mockUserService = Mockito.mock(UserService.class);
        spyUserService =  Mockito.spy(UserService.class);
    }
    
    @Test
    public void test1(){
        System.out.println("Mockito.mockingDetails(mockUserService).isMock() = " + Mockito.mockingDetails(mockUserService).isMock());
        System.out.println("Mockito.mockingDetails(mockUserService).isSpy() = " + Mockito.mockingDetails(mockUserService).isSpy());
        System.out.println("Mockito.mockingDetails(spyUserService).isMock() = " + Mockito.mockingDetails(spyUserService).isMock());
    }
}

方法三:MockitoAnnotations.openMocks(this)+@Mock等注解

java 复制代码
public class InitMockSpyMethod1{
    @mock
    private UserService mockUserService;
    @Spy
    private UserService spyUserService;

    @BeforeEach
    public void init(){
        MockitoAnnotations.openMocks(this);
    }
    
    @Test
    public void test1(){
        //true     判断某对象是不是mock对象
        System.out.println("Mockito.mockingDetails(mockUserService).isMock() = " + Mockito.mockingDetails(mockUserService).isMock());

        //false     判断某对象是不是apy对象
        System.out.println("Mockito.mockingDetails(mockUserService).isMock() = " + Mockito.mockingDetails(mockUserService).isMock());

        //true     判断某对象是不是spy对象
        System.out.println("Mockito.mockingDetails(spyUserService).isSpy() = " + Mockito.mockingDetails(spyUserService).isSpy());

        //true     判断某对象是不是spy对象,因为spy是一种特殊的mock(spy对象是另一种不同类型的mock对象)
        System.out.println("Mockito.mockingDetails(spyUserService).isMock() = " + Mockito.mockingDetail(spyUserService).isMock());
    }
}

五:参数匹配

参数匹配指的是:通过方法签名(参数)来指定哪些方法调用需要处理被处理(插桩、verify验证)

对于mock对象不会调用真实方法,直接返回mock对象的默认值;

默认值(int)、null(UserVO)、空集合(list)

六:方法插桩

指定调用某个方法时的行为(stubbing),达到相互隔离的目的

java 复制代码
/**
*测试插桩时的参数匹配
*/
@Test
public void test2(){
    UserUpdateReq userUpdateReq1 = new UserUpdateReq();
    userUpdateReq1.setId(1L);
    userUpdateReq1.setPhone("1L");
    //指定参数为userUpdateReq1时调用mockUserService.modifyById(userUpdateReq1);
    Mockito.doReturn(99).when(mockUserService).modifyById(userUpdateReq1);
    int result1 = mockUserService.modifyById(userUpdateReq1);
    //运行结果为99
    System.out.println("result1 = " + result1);
    
    UserUpdateReq userUpdateReq2 = new UserUpdateReq();
    userUpdateReq2.setId(2L);
    userUpdateReq2.setPhone("2L");
    int result2 = mockUserService.modifyById(userUpdateReq2);
    //运行结果为0
    System.out.println("result2 = " + result2);
}

总结:

是告诉mockito当传入的是下面的参数这个类型时,才对其进插桩,若不是这个值,则不用对它进行插桩

若想要拦截某种类型的任意对象,则需要运用到:

ArgumentMatchers.any拦截UserUpdateReq类型的任意对象

校验:

add方法调用一次,校验通过。

java 复制代码
  private UserService mockUserService;

    @Test
    public void test4(){
        List<String> features = new ArrayList<>();
        mockUserService.add("实验","123",festures);

        //校验参数为"乐之者","123",festures,features的add方法调用了1次
        Mockito.verify(mockUserService,Mockito.times(2)).add("实验","123",festures);

        //报错  ,要么都用要么就都别用
       // Mockito.verify(mockUserService,Mockito.times(2)).add("实验","123",festures);

        //此时可以校验通过
         Mockito.verify(mockUserService,Mockito.times(2)).add(anyString(),anyString(),anyList());
    }
    
}

但是有一点需要牢记的,除了any,还有(anyLong,anyString...),注意他们都不包括null,如果传null,还是不能被匹配的。

通过插桩,指定方法的返回值:

void返回值方法插桩 :

插桩的两种方式:

多次插桩:

其中when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3)可以简写为when(mockList.size()).thenReturn(1,2,3);

thenAnswer指定插桩逻辑:

执行真正的原始方法:

verify的使用:

@InjectMocks注解的使用

1.被@InjectMocks标注的属性必须是实现类,因为mockito会创建对应的实例对象,默认创建的对象就是未经过mockito处理的普通对象。因此常配合@spy注解使其变成默认调用真实方法的mock对象。

2.mockito会使用spy,最终的结果就是,会把userFeatureService给注入到InjectMocks标注的变量所对应的对象里面去。

注入的原理:

相关推荐
网络毒刘13 小时前
端到端:用 Cursor Agent 完成「小功能 + 单测 + PR 描述」并附人工验收清单
单元测试·agent·ai编程·cursor·工具实践
mont evergreen13 小时前
RTMP 连接单元测试之旅
单元测试·音视频·源码解读
我就是不信1 天前
CuTest:轻量级 C 语言单元测试框架实战指南
c语言·单元测试·log4j
众链网络2 天前
大客流票务系统怎么防超卖:库存并发、候补队列与幂等设计的工程取舍
junit·架构
深念Y4 天前
rime-雾凇拼音-配置记录
linux·junit·软件·拼音·kde
虫无涯5 天前
Coverity 如何结合 GJB8114-2013使用?
git·单元测试·嵌入式测试·coverity·静态扫描
白山编程大哥7 天前
Lua 中使用 C 语言的用户自定义类型——userdata
c语言·junit·lua
茉莉玫瑰花茶8 天前
GO [ 单元测试 ]
golang·单元测试·log4j
测试员周周9 天前
【jev应用到测试】94 条 UI 报错交给 Jev,三档判对 85.1%,开单闸门定在 0.6
人工智能·python·测试工具·单元测试·测试用例
行者游学10 天前
Kong 插件,解决2.5.1不支持原生暴露url
junit·kong