mock静态方法
有时候代码中需要调用工具类的某个静态函数,但是这个函数非常复杂, 或者依赖了一些其他的东西, 我们希望”屏蔽”掉这个函数调用, 而让代码能够继续运行下去, 那么可以mock掉这个函数调用.
假设, 工具类为 MyUtil.java
, 需要mock的方法为methodA()
和 methodB(Object arg1, Object arg2)
, 单元测试类为MyClassTest
.
步骤
- 首先在单元测试类
MyClassTest
上面添加注解@PrepareForTest(MyUtil.class)
, 注意不要在测试方法上面加, 可能会报错. - 在
@Before
注解的方法里面初始化mockmockStatic(MyUtil.class)
, 注意每一个包含了静态方法的类都要初始化mock. - 在测试方法里面进行mock处理.
第3步中有两种处理方式:
如果静态方法的返回值为
void
, 我们可能只想跳过这个静态方法, 可以通过以下方式mockPowerMockito.doNothing().when(MyUtil.class, "methodA");
如果静态方法有返回值, 我们可以使用
when().thReturn()
的方式:PowerMockito.when(MyUtil.class, "methodB", arg1, arg2).thenReturn("你想要返回的内容");
阻止静态初始化, 包括static{}静态代码块或者static变量的初始化
static{}静态代码块或者static变量的初始化, 在class被容器load的时候就要被执行,如果执行错误就会导致junit单元测试错误 .
解决方法如下:
@SuppressStaticInitializationFor("com.xxx.test.MyStaticClass") //阻止静态初始化
mock构造函数
有时候在我们的测试代码内部通过new
调用构造函数生成临时对象, 这样的对象无法通过在方法调用之前mock出来, 所以需要借助PowerMock
来处理.
假设, 被测试的类为MyRealClass.java
, 针对被测试类我们编写的单元测试类为MyRealClassTest.java
, 在MyRealClass.java
的某个被测试方法中调用了 new SomeClass(Object arg1, Object arg2)
来创建临时对象.
步骤
首先需要在单元测试类上
MyRealClassTest.java
使用@PrepareForTest(MyRealClass.class)
注解, 注意这里不是SomeClass.class
, 这里填的应该是调用SomeClass.class
的调用者类, 也就是MyRealClass.class
.然后通过
whenNew().withAnyArguments().thenReturn()
来mock:PowerMockito.whenNew(SomeClass.class).withAnyArguments().thenReturn("任何你想返回的对象");
注意这里, 我不想关心参数是什么, 所以使用的是
withAnyArguments()
, 其实这样特别省事儿, 可以不用构造参数对象, 用起来很爽快!如果真要去关心参数情况, 可以使用其他几种,比如
withNoArguments()
,withArguments
,withParameterTypes
.
打破封装(进阶)
翻译: Instant Mock Testing with PowerMock
封装是面向对象编程的基本原则之一. 在一个好的面向对象的设计, 我们最终会得到一些重要的私有方法, 涉及具有重要状态信息的业务操作和字段. 有时, 单独测试它们是非常重要的。接下来, 我们将学习如何编写这种情况下的测试.
例子
首先定义我们的实体类
Department
:public class Department { private List<Employee> employees = new ArrayList<>(); /** * 最高薪水 */ private long maxSalaryOffered; public void addEmployee(final Employee employee) { employees.add(employee); updateMaxSalaryOffered(); } private void updateMaxSalaryOffered() { maxSalaryOffered = 0; for (Employee employee : employees) { if (employee.getSalary() > maxSalaryOffered) { maxSalaryOffered = employee.getSalary(); } } } public long getMaxSalaryOffered() { return maxSalaryOffered; } }
Department
类有两个方法:addEmployee
: 这个方法向Department
对象中的私有List
对象employees
中添加一个employee
.updateMaxSalaryOffered
: 这个方法更新Department
提供的最高薪水.
首先我们来测试
addEmployee
方法:@Test public void shouldVerifyThatNewEmployeeIsAddedToTheDepartment() { final Department department = new Department(); final Employee employee = new Employee(); // 向department中添加一个employee department.addEmployee(employee); // 从Department实例中获取私有的成员employees final List<Employee> employees = Whitebox.getInternalState(department, "employees"); // 设置断言,验证employee是否被添加到了employees中 Assert.assertTrue(employees.contains(employee)); } @Test public void shouldAddNewEmployeeToTheDepartment() { final Department department = new Department(); final Employee employee = new Employee(); final ArrayList<Employee> employees = new ArrayList<>(); // 设置私有属性employees Whitebox.setInternalState(department, "employees", employees); // 添加employee department.addEmployee(employee); // 设置断言,验证employee是否被添加到了employees中 Assert.assertTrue(employees.contains(employee)); }
我们可以通过两个途径来测试
addEmployee
方法:方案一:
- 首先向
department
中添加employee
. - 然后通过使用
Whitebox.getInternalState
方法从department
中获得私有成员employees
. - 最后通过断言判断.
- 首先向
方案二:
- 首先通过
Whitebox.setInternalState
替换department
中的employees
. - 然后向
department
中添加employee
. - 最后通过断言判断.
- 首先通过
最后, 我们来测试
Department
类的私有方法updateMaxSalaryOffered
:@Test public void shouldVerifyThatMaxSalaryOfferedForADepartmentIsCalculatedCorrectly() throws Exception { final Department department = new Department(); final Employee employee1 = new Employee(); final Employee employee2 = new Employee(); employee1.setSalary(60000); employee2.setSalary(65000); // 添加employee1和employee2 final ArrayList<Employee> employees = new ArrayList<>(); employees.add(employee1); employees.add(employee2); // 替换department的私有成员employees Whitebox.setInternalState(department, "employees", employees); // 调用私有方法updateMaxSalaryOffered Whitebox.invokeMethod(department, "updateMaxSalaryOffered"); // 获取私有成员maxSalaryOffered的值 final long maxSalary = Whitebox..getInternalState(department, "maxSalaryOffered"); // 断言 Assert.assertEquals(65000, maxSalary); }
抑制不想要的行为
翻译: Instant Mock Testing with PowerMock
有时, 我们可能需要抑制构造器方法、方法、字段或静态初始化器, 因为它们执行一些操作对于执行单元来说不是很理想测试他们自己的代码。在处理第三方库或处理第三方库时,可能会出现这种情况
本节, 我们将讨论如何抑制这些行为.
例子
首先, 我们修改Department
类, 让它继承一个基类BaseEntity
, 这个基类使得Department
的单元测试难以进行.
BaseEntity
类如下:public class BaseEntity { /** * 默认构造函数抛出UnsupportedOperationException异常 */ public BaseEntity() { throw new UnsupportedOperationException(); } }
这是一个简单的基类, 它的默认构造函数抛出一个
UnsupportedOperationException
异常.修改后的
Department
类如下:public class Department extends BaseEntity { private int departmentId; private List<Employee> employees = new ArrayList<>(); /** * 最高薪水 */ private long maxSalaryOffered; // 构造函数 public Department(int departmentId) { super(); this.departmentId = departmentId; } public void addEmployee(final Employee employee) { employees.add(employee); updateMaxSalaryOffered(); } private void updateMaxSalaryOffered() { maxSalaryOffered = 0; for (Employee employee : employees) { if (employee.getSalary() > maxSalaryOffered) { maxSalaryOffered = employee.getSalary(); } } } public long getMaxSalaryOffered() { return maxSalaryOffered; } public int getDepartmentId() { return this.departmentId; } }
唯一的改变就是
Department
现在继承BaseEntity
.由于BaseEntity
的构造函数会抛出异常, 任何人试图实例化Department
的时候都会获得这个异常.我们在
Department
的构造函数中也加入了对departmentId
成员的初始化, 但是是在调用super()
构造函数之后初始化.如果我们就这样为
Department
d的构造函数编写测试用例, 我们会获得抛出的异常.为了给
Department
的构造函数编写测试用例, 我们需要”抑制”基类BaseEntity
的构造函数的调用:@RunWith(PowerMockRunner.class) @PrepareForTest(Department.class) public class DepartmentTest { @Test public void shouldSuppressTheBaseConstructorOfDepartment() { // 抑制基类的行为 PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class)); Assert.assertEquals(10, new Department(10).getDepartmentId()); } }
让我们为
Department
添加一个方法:public void setName(String name) { this,name = name; super.performAudit(this.name); }
Department
的setName
方法是一个很简单的setter方法, 在setName
方法中调用了super.performAudit(this.name)
方法,这个方法是在基类中定义的.protected void performAudit(String auditInformation) { throw new UnsupportedOperationException(); }
测试
Department
的setName
方法的测试代码为:@Test public void shouldSuppressThePerformAuditMethodOfBaseEntity() { // 抑制基类的行为 PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class)); PowerMockito.suppress(PowerMockito.method(BaseEntity.class, "performAudit", String.class)); final Department department = new Department(); department.setName("Mocking with PowerMock."); Assert.assertEquals("Mocking with PowerMock.", department.getName()); }
让我们再来改变一下
BaseEntity
类, 添加一个静态代码块.public class BaseEntity { static { String x = null; x.toString(); } // 省略剩下的代码... }
真如上面所展示的, 静态代码块在执行的时候会抛出
NullPointerException
异常.抑制静态代码块的测试应该像下面一样:
@RunWith(PowerMockRunner.class) @PrepareForTest(Department.class) @SuppressStaticInitializationFor("com.gitshah.powermock.BaseEntity") public class DepartmentTest { @Test public void shouldSuppressTheInitializerForBaseEntity() { PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class)); Assert.assertNotNull(new Department()); } }
通过
@SuppressStaticInitializationFor
注解来抑制静态初始化, 包括静态代码和静态变量的初始化.