使用new MockUp功能可以比较容易的对具体的方法进行改写,但也存在不方便的地方。
- 需要到具体实现类上,拷贝要mock的方法签名;而且如果方法名称或参数变化了,在MockUp里面并不能检测出来。
- 无法指定校验调用次数
在Fluent Mock框架中引入一个新的注解 @Mocks,可以很方便的解决上述问题。 对上篇文章中的mock示例, 我们用 @Mocks来实现一遍,就能对比出2者的差异。
各种限定类型方法mock演示
对静态代码块的mock
我们先建立一个测试类,并在类上定义@Mocks,value值是要mock的类列表
@Mocks(AnNormalService.class)
public class AnNormalServiceFluentMockUpTest {
// ...
}
编译该类, AnnotationProcessor会在target/generated-test-sources目录下生成2个文件
- 在@Mocks注解里面声明的需mock类快捷定义类
- 对应的类名称+MockUp后缀的MockUp列表,这些类路径是被mock类路径加前缀"mock."。
- 在示例中是 AnNormalServiceMockUp.java
- 这些类继承自FluentMockUp类,里面定义了对每一个方法进行mock的快捷方法。
- *MockUp引导类
- 声明@Mocks类类名+Mocks后缀的类,这个类路径和声明类路径一样。
- 在示例中是 AnNormalServiceFluentMockUpTestMocks.java
- 可以在Test中声明,方便引用需要mock的类
@Mocks(AnNormalService.class)
public class AnNormalServiceFluentMockUpTest {
AnNormalServiceFluentMockUpTestMocks mocks = new AnNormalServiceFluentMockUpTestMocks();
// ...
}
各种限定类型方法mock演示
演示对public和final方法mock
@Mocks(AnNormalService.class)
public class AnNormalServiceFluentMockUpTest {
AnNormalServiceFluentMockUpTestMocks mocks = new AnNormalServiceFluentMockUpTestMocks();
@DisplayName("演示对public和final方法mock")
@Test
void test3() {
mocks.AnNormalService().publicMethod.restReturn(5);
mocks.AnNormalService().finalMethod.restReturn(6);
AnNormalService service = new AnNormalService(400);
Assertions.assertEquals(5, service.publicMethod());
Assertions.assertEquals(6, service.finalMethod());
}
}
示例中mock了AnNormalService2个方法:
- mock方法publicMethod, 始终返回模拟值5
- mock方法finalMethod, 始终返回模拟值6
[对应MockUp的例子](mockup-advanced.md#mock public和final方法)
FluentMockUp提供了3种行为模拟API
- 直接返回值
- thenReturn(Object... ), 按声明顺序和调用顺序匹配方法,逐次返回模拟值。
- restReturn(Object), 除了指定的then行为外,剩下的调用都返回指定模拟obj值。
- 抛出异常
- thenThrows(Throwable...), 按声明顺序和调用顺序匹配方法,逐次抛出异常。
- restThrows(Throwable), 除了指定的then行为外,剩下的调用都抛出指定模拟异常。
- 定制行为
- thenAnswer(FakeFunction...), 按声明顺序和调用顺序匹配方法,逐次模拟行为。
- restAnswer(FakeFunction), 除了指定的then行为外,剩下的调用都执行的模拟行为。
演示对静态方法的mock
@Mocks(AnNormalService.class)
public class AnNormalServiceFluentMockUpTest {
AnNormalServiceFluentMockUpTestMocks mocks = new AnNormalServiceFluentMockUpTestMocks();
@DisplayName("演示对静态方法的mock")
@Test
void test5() {
mocks.AnNormalService().getStaticInt
.thenAnswer(inv -> {
return (int) inv.proceed() + 4;
})
.thenAnswer(inv -> {
return (int) inv.proceed() + 100;
})
.restAnswer(inv -> {
return (int) inv.proceed() * 100;
});
AnNormalService.setStaticInt(2);
Assertions.assertEquals(6, AnNormalService.getStaticInt());
Assertions.assertEquals(102, AnNormalService.getStaticInt());
Assertions.assertEquals(200, AnNormalService.getStaticInt());
Assertions.assertEquals(200, AnNormalService.getStaticInt());
}
}
示例中:
- 第一次调用模拟逻辑: 原接口返回值的 + 4, 这里期望值是6。
- 第二次调用模拟逻辑: 原接口返回值 + 100, 这里期望值是102。
- 剩余的调用模拟逻辑: 原接口返回值 * 100, 这里期望值是200。
thenAnswer和restAnswer入参是接口函数 FakeFunction
@FunctionalInterface
public interface FakeFunction<R> extends MockBehavior {
/**
* 定义mock方法实现
*
* @param it mock method proxy
* @return
*/
R doFake(Invocation it);
}
对方法入参进行断言
在thenAnswer和restAnswer中,可以通过Invocation对象获得入参,并对入参进行断言判断。
@Mocks(InvocationFluentMockDemo.Service.class)
public class InvocationFluentMockDemo {
InvocationFluentMockDemoMocks mocks = new InvocationFluentMockDemoMocks();
@DisplayName("对方法入参进行断言演示")
@Test
void test3() {
mocks.Service().setString.restAnswer(inv -> {
Assertions.assertEquals("期望值", inv.arg(0));
return inv.proceed();
});
Service service = new Service();
// 正常通过
service.setString("期望值");
// 设置其它值, 应该是抛出一个断言异常
Assertions.assertThrows(AssertionError.class, () -> service.setString("其它值"));
}
}
- inv.args()返回 Object[] args入参数组
- inv.arg(int index)返回第index个入参, index从0开始
对方法执行结果断言
@Mocks(InvocationFluentMockDemo.Service.class)
public class InvocationFluentMockDemo {
InvocationFluentMockDemoMocks mocks = new InvocationFluentMockDemoMocks();
@DisplayName("对方法出参进行断言演示")
@Test
void test4() {
mocks.Service().getString.restAnswer(inv -> {
String result = inv.proceed();
Assertions.assertEquals("origin string", result);
return result;
});
Service service = new Service();
/**
* 方法调用正常通过
*/
service.getString();
/**
* 将返回值改为其它值, 方法调用应该抛出断言异常
*/
service.setString("other value");
Assertions.assertThrows(AssertionError.class, () -> service.getString());
}
}
对所有的调用参数进行断言
语法: .assertParameters(inv->{ 具体断言 }) 示例:
@Mocks(MyServiceImpl.class)
public class AssertAroundTest {
AssertAroundTestMocks mocks = new AssertAroundTestMocks();
@Test
void test_assertBefore() {
mocks.MyServiceImpl().sayHello
.assertParameters(invocation -> {
String name = invocation.arg(0);
Assertions.assertEquals("are u ok!", name);
});
new MyServiceImpl<>().sayHello("are u ok!");
}
}
assertParameters方法会对指定方法的所有调用进行判断,不管是指定了mock逻辑,还是执行原方法逻辑。 上面的示例,就没有指定sayHello的模拟逻辑,但同样会进行拦截,然后做参数校验。
对执行结果进行校验
语法: assertResult((inv,result)->{}) 示例:
@Mocks(MyServiceImpl.class)
public class AssertAroundTest {
AssertAroundTestMocks mocks = new AssertAroundTestMocks();
@Test
void test_assertAfter() {
mocks.MyServiceImpl().sayHello
.assertResult((inv, result) -> {
Assertions.assertEquals("hello:are u ok!", result);
});
new MyServiceImpl<>().sayHello("are u ok!");
}
}