背景
Mockito是一个流行的Java测试框架,它通过提供简洁的API来帮助开发者编写单元测试。以下是Mockito的主要特点和用法:
-
创建模拟对象:使用Mockito可以轻松地创建模拟对象,这些对象可以模拟真实对象的行为,用于替代真实对象的依赖项,以便进行独立的单元测试。
-
部分模拟对象:Mockito还允许创建部分模拟对象,即spy对象。Spy对象保留了真实对象的部分行为,同时可以模拟方法的返回值。
-
依赖注入:Mockito提供了@InjectMocks注解,用于自动注入模拟对象到待测试的类中,简化了测试代码的编写过程。
-
验证行为:Mockito提供了丰富的方法来验证对象的行为,确保在测试过程中对象的方法被正确地调用。
-
降低耦合度:通过使用Mockito,可以虚拟出外部依赖,降低测试组件之间的耦合度,使测试更加专注于代码的流程与结果。
-
创建方式:Mockito提供了多种创建模拟对象的方式,包括使用Mockito.mock()方法或@Mock注解。这两种方式都可以有效地创建所需的模拟对象。
总的来说,Mockito通过提供强大的功能和方法,极大地简化了Java项目中单元测试的编写,使得开发者能够更加高效地进行测试驱动的开发。
使用场景
Mockito的使用场景包括但不限于以下几点:
-
单元测试:在单元测试中,Mockito可以用来模拟复杂的依赖关系,确保测试的独立性和可靠性。通过模拟依赖对象的行为,可以专注于测试目标类的逻辑,而不受外部因素影响。
集成测试:在集成测试中,Mockito可以用来模拟外部系统或服务的行为,这样可以在不实际调用外部系统的情况下验证系统的集成逻辑。
-
系统测试:在系统测试中,Mockito可以用来模拟用户行为或特定的系统事件,以便在控制的环境下测试系统的整体行为。
-
团队并行开发:当前后端或不同后端服务之间需要并行开发时,Mockito可以帮助开发者通过模拟接口来独立工作,从而加快开发进度并减少等待时间。
-
测试驱动开发(TDD):在TDD实践中,开发人员在编写实际代码之前先编写测试用例。有了Mockito,即使某些功能尚未实现,也可以提前创建测试用例,因为可以模拟尚未完成的部分。
-
隔离系统:当测试需要调用可能会对系统产生副作用的操作(如修改数据库)时,可以使用Mockito来创建一个虚拟的操作,避免对实际系统造成影响。
模拟访问资源:如果需要测试的代码依赖于访问外部资源(如网络请求),使用Mockito可以模拟这些资源,避免因外部因素导致的测试不稳定。
-
提高测试覆盖度:对于一些难以触发的场景(如服务器错误),可以使用Mockito来模拟这些情况,以确保测试用例的全面覆盖。
使用示例
@Mock注解
- 最小化重复的模拟创建代码。
- 使测试类更具可读性。
- 使验证错误更容易读取,因为字段名称用于标识mock。
java
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;
@Mock private ArticleDatabase database;
@Mock private UserProvider userProvider;
private ArticleManager manager;
@org.junit.jupiter.api.Test
void testSomethingInJunit5(@Mock ArticleDatabase database) {
带有回调的存根
允许使用通用Answer接口进行存根处理。这里有一个示例:thenReturn()thenThrow()
java
when(mock.someMethod(anyString())).thenAnswer(
new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + Arrays.toString(args);
}
});
//Following prints "called with arguments: [foo]"
System.out.println(mock.someMethod("foo"));
方法返回结果
对于任何方法,都可以使用doThrow(), doAnswer(), doNothing(), doReturn() 和doCallRealMethod()来代替带有when()的相应调用。
java
doThrow(new RuntimeException()).when(mockedList).clear();
//following throws RuntimeException:
mockedList.clear();
spy
Mockito中的spy对象是一种对真实对象进行部分模拟的方式,它允许在不改变原有类行为的基础上,对特定方法进行监控和验证。
使用spy对象的目的是为了在不完全模拟一个对象的同时,能够对某些特定的行为进行监控或者验证。以下是spy对象与mock对象的区别:
-
默认行为:对于未指定的方法,spy对象会调用真实的方法并返回真实的值,而mock对象则不执行任何操作,对于有返回值的方法,默认返回null。
-
使用方式:在spy对象中,通常使用doReturn...when语句来指定特定方法的行为,这样可以确保真实的方法不被执行。而在mock对象中,使用when...thenReturn语句来定义方法的行为,这样私有方法不会被执行。
-
覆盖率统计:当使用spy对象时,原有的方法仍然会被执行,因此可以统计到代码覆盖率。而shadow对象则无法被统计覆盖率,因为它完全替换了原有对象的行为。
java
List list = new LinkedList();
List spy = spy(list);
//可以选择截取一些方法:
when(spy.size()).thenReturn(100);
//使用spy调用*real*方法
spy.add("one");
spy.add("two");
//打印"一"-列表的第一个元素
System.out.println(spy.get(0));
//size()方法已被存根-已打印100
System.out.println(spy.size());
//可选,可以验证
verify(spy).add("one");
verify(spy).add("two");
模拟静态方法
当使用内联mock maker时,可以在当前线程和用户定义的范围内模拟静态方法调用。通过这种方式,Mockito确保并发和顺序运行的测试不会干扰。为了确保静态mock保持临时性,建议在try-with-resources构造中定义作用域。
java
assertEquals("foo", Foo.method());
try (MockedStatic mocked = mockStatic(Foo.class)) {
mocked.when(Foo::method).thenReturn("bar");
assertEquals("bar", Foo.method());
mocked.verify(Foo::method);
}
assertEquals("foo", Foo.method());
模拟构造方法
由于模拟构造的定义范围,一旦释放范围,对象构造就会返回到其原始行为。要定义mock行为并验证方法调用,使用返回的MockedConstruction。
java
assertEquals("foo", new Foo().method());
try (MockedConstruction mocked = mockConstruction(Foo.class)) {
Foo foo = new Foo();
when(foo.method()).thenReturn("bar");
assertEquals("bar", foo.method());
verify(foo).method();
}
assertEquals("foo", new Foo().method());