如果你在Spock中使用powermock去mock掉jdk类的静态方法,可能会出现powermock不兼容的问题(感谢"有1024个人"这位朋友的反馈,原文见之前这篇文章的评论:https://javakk.com/281.html)。
比如有下面这个要测试的类,里面调用了java.util.Calendar
获取当前的年份,其他代码会调用这个方法,我们可能需要对返回的年份进行mock来测试不同的业务逻辑:
public class CalendarUtils {
/**
* 获取当前年份
* @return
*/
public int getYear() {
return Calendar.getInstance().get(1);
}
}
一般对java.util.Calendar
、java.util.UUID
、java.sql.Date
等jdk系统类的静态方法mock,使用powermock实现的话类似下面这种写法:
PowerMockito.mockStatic(java.util.Calendar.class)
PowerMockito.when(java.util.Calendar.getInstance().get(1)).thenReturn(2020) // mock返回年份为2020年
这样的代码在Junit的单元测试里是能运行成功并返回指定的mock值2020
,但在Spock中使用可能会出现powermock不兼容的问题,异常类似下面:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);. . . . . .
原因是因为powermock的原理是对字节码进行重写,而Spock使用的Groovy语言本身是一种动态语言,他也会对测试类进行一些动态转换,这样的话在针对JDK系统类的重写时可能会有前后顺序不兼容的情况。
不过这种问题仅限于JDK/JRE系统类的mock,我们自己写的静态方法不会受影响。
具体原因可以参考这两个issue:
- https://github.com/spockframework/spock/issues/1014
- https://github.com/powermock/powermock/issues/1008
解决方法有两种,一种是修改powermock的用法,另外一种是使用Groovy的@CompileStatic
注解
第一种方式(主要是使用PowerMockito.stub
和method
方法代替PowerMockito.when
方法):
@PrepareForTest([CalendarUtils.class])
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
class MyTest extends Specification {
def cal = new CalendarUtils()
def "testCalendar" () {
given:
def calendar = Mock(Calendar)
PowerMockito.stub(PowerMockito.method(Calendar, "getInstance")).toReturn(calendar) // 使用stub替换when
calendar.get(_) >> 2020
when:
def year = cal.getYear()
then:
year == 2020
}
}
第二种方式(可以继续使用PowerMockito.when
方法,和Junit一样):
import groovy.transform.CompileStatic
......
@PrepareForTest([CalendarUtils.class])
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
class MyTest extends Specification {
def cal = new CalendarUtils()
def "testCalendar" () {
given:
mockStatic(2020)
when:
def year = cal.getYear()
then:
year == 2020
}
@CompileStatic
def mockStatic(int year) {
PowerMockito.mockStatic(Calendar)
PowerMockito.when(Calendar.getInstance().get(Mockito.anyInt())).thenReturn(year)
}
}
注意:@PrepareForTest([CalendarUtils.class])
参数是你要测试的类(调用Calendar
等系统类的类CalendarUtils
),而不是Calendar
类本身。
这两种写法也都支持Spock的where表格功能。
虽然这个问题解决了,但是写到这里感觉越走越偏了:
正常来说静态方法是不需要被mock的,静态方法本身应该是无状态的、纯计算、不依赖第三方资源配置,无副作用(不会修改全局变量)的函数
但是包括我们自己写的静态方法也存在类似的问题,这也是Spock不去支持静态方法mock的原因。
我们写单测的目的到底是为了什么?
- 仅仅是为了达到测试覆盖率吗?
- 不管业务代码写的多么不合理,只要单元测试能跑到就行?
单元测试有一个很重要的作用是督促我们写出更合理的代码(高内聚、松耦合、易测试)
如果我们写的业务代码不好测试(不具有可测性),首先考虑的应该是重构
就好像"我没时间磨斧子,因为我忙着砍树"一样,颠倒了主次。
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2038.html
暂无评论