Spring Boot测试打桩-Mockito在Spring Boot中的常见测试场景

本文主要分析Mockito在Spring Boot测试中的应用;
关于Spring Boot基本测试场景及说明可以参考此文
本文基于Spring Boot+Kotlin实现相关示例,工程源代码: https://github.net/icarusliu/learn
所有关于本文描述的测试入口均在类:TestLearninterceptorApplication中。

0 概述

Mockito用于测试时进行打桩处理;通过它可以指定某个类的某个方法在什么情况下返回什么样的值。
一般在单元测试时,只需要关注当前正在测试的类;但这个测试类可能会使用到多个其它类,如本文示例中TestController2类使用了TestService;TestService的类的测试应该在其本身的单元测试类中进行,而在测试TestController2时,应当尽量避免TestService带来各种影响,否则随着引用类的增加,测试场景会越来越复杂。这个时候就可以假设TestService在某些场景下会返回某些值,从而来降低引用类所带来的测试复杂度增加的影响。Mockito就用于这种场景。

Mockito常用测试场景描述如下:
- 指定打桩对象的返回值
- 判断某个打桩对象的某个方法被调用及调用的次数
- 指定打桩对象抛出某个特定异常

Mockito的使用,一般有以下几种组合:
- do/when:包括doThrow(…).when(…)/doReturn(…).when(…)/doAnswer(…).when(…)
- given/will:包括given(…).willReturn(…)/given(…).willAnswer(…)
- when/then: 包括when(…).thenReturn(…)/when(…).thenAnswer(…)
关于各个用法详见后续描述。

本文示例中测试的对象包括TestController2/TestService; 具体实现见GITHUB工程; 其中TestController2的testService方法中,包含对TestService对象的test方法调用:

@RequestMapping("/service")
@Throws(Exception::class)
fun testService(): String {
    return testService.test("test")
}

TestService的test方法实现如下:

@Throws(Exception::class)
fun test(str: String): String {
    return str
}

1 指定打桩对象返回值

通过Mockito指定打桩对象的返回值时,可以通过以下方式进行:

1.1 given

given用于对指定方法进行返回值的定制,它需要与will开头的方法一起使用,will开头的方式根据其接收参数的不同,又分成两类:一是接收直接值的,如直接指定返回结果为某个常量;二是接收Answer参数的,可以骑过Answer类的answer方法来根据传入参数定制返回结果。

先来讲下Answer的使用。

1.1.1 Answer对象

我们实际针对的一般是某个类的某个方法;这个方法可能会有输入参数;考虑这种场景:如果要假设打桩的这个方法,在某个输入时返回值A;在另外一个输入时返回值为B;这种场景就可以通过Answer类来实现。

我们下面将要对TestService的test方法进行打桩:

BDDMockito.given(this.testService.test(BDDMockito.anyString())).willAnswer(Answer{
    Assert.assertEquals(it.arguments.size, 1)
    val args = it.arguments[0]
    Assert.assertNotNull(args)

    "mockTest $args test"
})

Assert.assertEquals(testService.test("test"), "mockTest test test")
Assert.assertEquals(testService.test("1"), "mockTest 1 test")

上例中,通过given来指定需要打桩的方法是哪个,然后通过willAnswer来定制这个方法的返回值是什么:根据传入的参数来组装实际的返回结果。如传入的参数是test,那么在测试调用这个方法时返回的应该是”mockTest test test”;如果传入的是1,那么返回的应该是”mockTest 1 test”;

可以看到上例中使用到了it对象,这个是lambda语法,由于Answer对象只有一个方法,因此可以直接使用这种方式。it表示的Answer的方法中的输入参数。我们在Answer的源码中看到这个对象类型是: InvocationOnMock; 通过它可以获取打桩方法的实际传入参数清单。如本例 中,it.arguments[0]表示获取的是调用testService.test(“test”)的时候传入的参数,其值是”test”。

通过Answer,我们可以在测试我们的主类时,尽量多的覆盖其各个路径。

下面来讲述given如何打桩, 如在测试TestController2时,如果需要指定testService.test返回的值,可以通过以下方式实现:

1.1.2 given + willReturn

通过willReturn可以直接指定打桩的方法的返回值,如示例:

BDDMockito.given(this.testService.test(BDDMockito.anyString())).willReturn("testMock")
mockMvcExpect(url, "testMock")

意思是指定testService的test方法在任何场景下都返回”testMock”常量。

其中mockMvcExpect方法定义如下:

/**
 * 断定MVC请求成功且返回指定的字符串
 */
fun mockMvcExpect(url: String, expectedStr: String) {
    mvc.perform(MockMvcRequestBuilders.get(url))
            .andExpect(MockMvcResultMatchers.status().isOk)
            .andExpect(MockMvcResultMatchers.content().string(expectedStr))
}

1.1.3 given + willAnswer/will

如果上定制返回结果,可以通过willAnswer或者其简写will来实现,详细示例如下:

BDDMockito.given(this.testService.test(BDDMockito.anyString())).willAnswer({
    Assert.assertEquals(it.arguments.size, 1)
    val args = it.arguments[0]
    Assert.assertNotNull(args)

    "mockTest $args test"
})

mockMvcExpect(url, "mockTest test test")

该场景主要就是Answer的使用,请参考1.1.1章节。

1.2 when

when的作用与Given有点类似,但它一般与then开头的方法一起使用。

1.2.1 when + thenReturn

thenReturn与willReturn类似,不过它一般与when一起使用。 示例如下: 

BDDMockito.`when`(testService.test(BDDMockito.anyString())).thenReturn("testWhen")
mockMvcExpect(url, "testWhen")

1.2.2 when + thenAnswer/then

thenAnswer与willAnswer也类似:

BDDMockito.`when`(testService.test(BDDMockito.anyString())).thenAnswer({
    Assert.assertEquals(it.arguments.size, 1)
    Assert.assertNotNull(it.arguments[0])

    "testWhen ${it.arguments[0]}"
})
mockMvcExpect(url, "testWhen test")

1.2.3 do+when

包括doReturn及doAnswer两种,其中doAnswer实现如下: 

BDDMockito.doAnswer({
    Assert.assertEquals(it.arguments.size, 1)
    Assert.assertNotNull(it.arguments[0])

    "testWhen ${it.arguments[0]}"
}).`when`(testService.test(BDDMockito.anyString()))
mockMvcExpect(url, "testWhen test")

2 判断某个打桩对象的某个方法被调用及调用的次数

通过verify可以断言打桩方法是否执行及执行的次数。
如以下示例:

fun testVerify() {
    BDDMockito.given(testService.test(BDDMockito.anyString())).willReturn("testVerify")
    mockMvcExpect(url, "testVerify")

    //1. 判断testService的test方法被调用
    BDDMockito.verify(testService).test("test")

    //2. 判断testService的test方法被调用一次
    BDDMockito.verify(testService, BDDMockito.timeout(1)).test("test")
}

3 指定打桩对象抛出某个特定异常

通过when或者given也可以指定打桩方法抛出某个异常:

3.1 given

@Test(expected = Exception::class)
fun testGivenThrow() {
    BDDMockito.given(testService.test(BDDMockito.anyString())).willThrow(Exception("test"))
    testController.testService()
}

3.2 when

3.2.1 when/thenThrow

@Test(expected = Exception::class)
fun testThenThrow() {
    BDDMockito.`when`(testService.test("test")).thenThrow(Exception("test"))
    testController.testService()
}

3.2.2 doThrow/when

@Test(expected = Exception::class)
fun testDoThrow() {
    BDDMockito.doThrow(Exception("test")).`when`(testService.test("test"))
    testController.testService()
}
  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值