使用 Mockito 修改私有属性
修改私有属性来 Mock 可能不是一种很好的测试方式, 因为属性名是动态的,但有时不得已而为了,例如下面的代码:
测试时欲隔离对 ExternalApi 的外部依赖, 当然可以把它也作为构造函数的一个参数,这样创建 UserService 实例时就可以 Mock external 属性。不过 external 经常是不变的,所以作为方法参数的必要性也不大。这就希望能在构造出 UserService 之后对 external 私有属性进行 Mock 处理。
在 Mockito 1.x 和 2.x 下要使用不同的方式,分别使用到 Whitebox 和 FieldSetter 类,它们都来自于
如此这般,在 Mockito 2.x 下使用 FieldSetter 倒不如自己写一个 Whitebox 类只提供一个方法来修改私有属性,还无需在测试方法中自己处理异常.
所以 Mockito 2.x 可创建如下的 Whitebox 类来修改私有属性
其实私有属性可能经常变,在 IDE 中修改属性名时,用于反射时的属性字符串不会被自动修改也无妨,本来就是测试用例,跑一下测试用例就能发现问题了。
2020-06-03
<
它提供了更丰富的 API, 还不用自己写,避免犯错
永久链接 https://yanbin.blog/mockito-modify-private-field/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
1public class UserService {
2 private ExternalApi external = ExternalApi.default();
3 private UserDao userDao;
4
5 public UserService(UserDao userDao) {
6 this.userDao = userDao;
7 }
8
9 public User findUserById(int id) {
10 return userDao.findById(external.convertId(id));
11 }
12}测试时欲隔离对 ExternalApi 的外部依赖, 当然可以把它也作为构造函数的一个参数,这样创建 UserService 实例时就可以 Mock external 属性。不过 external 经常是不变的,所以作为方法参数的必要性也不大。这就希望能在构造出 UserService 之后对 external 私有属性进行 Mock 处理。
在 Mockito 1.x 和 2.x 下要使用不同的方式,分别使用到 Whitebox 和 FieldSetter 类,它们都来自于
mockito.internal.util.reflection 包,可见 Mockito 打心底不推荐直接使用它们,但谁叫它们是 public 的呢。还有一种方式是使用 PowerMock + Mockito, 这是后话。Mockito 1.x 修改私有属性
1import mockito.internal.util.reflection.Whitebox;
2.......
3
4public void testFindUserById() {
5 UserService userService = new UserService(Mockito.mock(UserDao.class));
6
7 ExternalApi external = Mockito.mock(ExternalApi.class);
8 Whitebox.setInternalState(userService, "external", external);
9
10 when(external.convertId(123)).thenReturn("xxx-123");
11 ........
12}Mockito 2.x 修改私有属性
Mockito 2.x 下移除了 Whitebox, 但可以用mockito.internal.util.reflection.FieldSetter, 它仅有的方法声明是public static void setField(Object target, Field field, Object value)这需要我们用反射的方式找到 Field, 还得处理下异常
1import mockito.internal.util.reflection.FieldSetter;
2......
3
4public void testFindUserById() throws NoSuchFieldException {
5 UserService userService = new UserService(Mockito.mock(UserDao.class));
6
7 Field apiField = UserService.class.getDeclaredField("external");
8 ExternalApi external = Mockito.mock(ExternalApi.class);
9 FieldSetter.setField(userService, apiField, external);
10
11 when(external.convertId(123)).thenReturn("xxx-123");
12 ......
13}如此这般,在 Mockito 2.x 下使用 FieldSetter 倒不如自己写一个 Whitebox 类只提供一个方法来修改私有属性,还无需在测试方法中自己处理异常.
所以 Mockito 2.x 可创建如下的 Whitebox 类来修改私有属性
1package cc.unmi.test;
2
3import java.lang.reflect.Field;
4import org.junit.Assert;
5
6public class Whitebox {
7 public static void setInternalState(Object target, String fieldName, Object value) {
8 try {
9 Field field = target.getClass().getDeclaredField(fieldName);
10 field.setAccessible(true); //粗爆的改成可访问,不管现有修饰
11 field.set(target, value);
12 } catch (Exception e) {
13 Assert.fail("Cannot change value of " + fieldName +" against target " + target);
14 }
15 }
16}其实私有属性可能经常变,在 IDE 中修改属性名时,用于反射时的属性字符串不会被自动修改也无妨,本来就是测试用例,跑一下测试用例就能发现问题了。
2020-06-03
使用 powermock-reflect 的 Whitebox
既然 Mockito 2 把 Whitebox 移除了,再创建一个 Whitebox 还不如使用另一个第三方测试好的 Whitebox 实现,那就是 powermock-reflect 的 Whitebox 类。我们不完全使用 PowerMockRunner, 只用到 powermock-reflect 模块,引入<
1<dependency>
2 <groupId>org.powermock</groupId>
3 <artifactId>powermock-reflect</artifactId>
4 <version>2.0.7</version>
5 <scope>test</scope>
6</dependency>它提供了更丰富的 API, 还不用自己写,避免犯错
永久链接 https://yanbin.blog/mockito-modify-private-field/, 来自 隔叶黄莺 Yanbin's Blog[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。