mockito 总结

本文详细介绍了Mockito框架的使用,包括mock和spy的区别,@Mock、@Spy、@InjectMocks等注解的生效方式和作用,when/then的两种形式,verify的多种验证方式,Argument matchers的使用,ArgumentCaptor获取参数,自定义mock返回值,以及如何处理难以mock的对象。提供了Mockito测试的重要知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 mock/spy

1.1 mock

List mockedList = mock(LinkedList.class);//可以mock具体类,建议
List interfaceList = mock(List.class);//也可以mock接口

//以下两个等效,并且是默认值
mock(LinkedList.class,Mockito.RETURNS_DEFAULTS);
mock(LinkedList.class,withSettings().defaultAnswer(Mockito.RETURNS_DEFAULTS));
//默认返回一个 SmartNull,可记录更详细的日志信息,mockito 4.0 后为默认
mock(LinkedList.class,Mockito.RETURNS_SMART_NULLS);
//如果返回 null,且不是 final,就返回一个 mock,不建议使用
mock(LinkedList.class,Mockito.RETURNS_MOCKS);
//可以级联调 when(),不建议使用
mock(LinkedList.class,Mockito.RETURNS_DEEP_STUBS);
//默认调用真实方法,这个方法等效于 spy
mock(LinkedList.class,Mockito.CALLS_REAL_METHODS);
//适用于 Builder 模式,一般用不上
mock(LinkedList.class,Mockito.RETURNS_SELF);

1.2 spy

//等效于:mock(ListedList.class,Mockito.CALLS_REAL_METHODS);
spy(LinkedList.class);
List list = new ArrayList();
spy(list);

1.3 @Mock/@Spy/@InjectMocks/@Captor/@MockBean/@SpyBean

1.3.1 如何使这些注解生效
  • 在测试类上使用:@RunWith(MockitoJUnitRunner.class)
  • 在 @Before 中调用:MockitoAnnotations.initMocks(this)
  • 在类中定义:@Rule public MockitoRule mockito = MockitoJUnit.rule();
1.3.2 作用
@Mock/@Spy:相当于 mock/spy 的快捷方式
@InjectMocks:注解的对象相对于 spy 对象,并且可以将 mock/spy 对应的对象自动注入到此对象对应的属性中
@Captor:可以获取 Matcher 实际执行时对应的参数
@MockBean/@SpyBean:相对于 @Mock/@Spy,并且此注释的对象,被加入到 spring 容器中
1.3.3 @InjectMocks 使用
  • https://www.jianshu.com/p/bb705a56f620

2 when/then

  • 有两种形式
    • doXXX().when(),推荐
    • when().doXXX(),不推荐(当调用真实方法时,会先直接when中的方法,这时的真实方法可能有问题,例如没有实现而报错)
    • any():可能造成 NPX
//调用 list.get() 三次以后都返回 third
doReturn("first","second","third").when(list).get(anyInt());
//当使用 matcher 时,所有参数都必须是 matcher 形式
//set(1,anyInt()) 将报错,可写成 set(eq(1),anyInt())
doThrow(new RuntimeException(),new IllegalAccessError()).when(list).set(anyInt(),anyInt())
//调用真实方法
doCallRealMethod().when(list).get(0);    
doNothing().when(list).add(anyInt());
//doAnswer
doAnswer(invocation -> 11).when(list).get(1);
assert 11 == (int)list.get(1);
assert list.get(2) == null;

3 verify

3.1 验证 mock/spy 对象已经执行了什么行为

 //验证 list 执行了 list.add("one")
 verify(list).add("one");
 //验证 list 执行了 list.clear()
 verify(list).clear();

3.2 验证行为执行的次数

//以下两个等效:验证 list.add("once") 执行了 1次
verify(list).add("once");
verify(list, times(1)).add("once");
//验证执行了 2 次
verify(list, times(2)).add("once");

verify(list, never()).add("never happened");
verify(list, atMostOnce()).add("once");
verify(list, atLeastOnce()).add("three times");
verify(list, atLeast(2)).add("three times");
verify(list, atMost(5)).add("three times");

//验证 listTwo,listThree 没有被调用过
verifyZeroInteractions(listTwo, listThree);

//验证已经验证了所有调用的方法了
verifyNoMoreInteractions(list);

3.3 验证调用顺序

//create an inOrder verifier for a single mock
InOrder inOrder = inOrder(list);
inOrder.verify(list).add("was added first");
inOrder.verify(list).add("was added second");

InOrder inOrder = inOrder(firstMock, secondMock);
//两个 mock 实例之间的方法的调用顺序
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");

3.4 延迟验证

  • 可用于异步情况,做延迟验证
//100ms 之后验证方法调用1次
verify(mock, timeout(100)).someMethod();
//100ms 之后验证方法调用2次
verify(mock, timeout(100).times(2)).someMethod();

3.5 忽略校验:ignoreStubs

3.5.1 忽略 verify
  • 忽略所有
//mocking lists for the sake of the example (if you mock List in real you will burn in hell)
List mock1 = mock(List.class), mock2 = mock(List.class);
//stubbing mocks:
when(mock1.get(0)).thenReturn(10);
when(mock2.get(0)).thenReturn(20);
//using mocks by calling stubbed get(0) methods:
System.out.println(mock1.get(0)); //prints 10
System.out.println(mock2.get(0)); //prints 20
//using mocks by calling clear() methods:
mock1.clear();
mock2.clear();
//verification:
verify(mock1).clear();
verify(mock2).clear();

//verifyNoMoreInteractions() fails because get() methods were not accounted for.
try { verifyNoMoreInteractions(mock1, mock2); } catch (NoInteractionsWanted e);

//因为忽略了 mock1 mock2,即使 get 方法没有 verify 也通过
verifyNoMoreInteractions(ignoreStubs(mock1, mock2));
verifyZeroInteractions() 等效于 verifyNoMoreInteractions
3.5.2 忽略 InOrder
  • 忽略所有
  List list = mock(List.class);
  when(list.get(0)).thenReturn("foo");
  list.add(0);
  list.clear();
  System.out.println(list.get(0)); //we don't want to verify this

  InOrder inOrder = inOrder(ignoreStubs(list));
  inOrder.verify(list).add(0);
  inOrder.verify(list).clear();
  inOrder.verifyNoMoreInteractions();
3.5.3 Strictness.STRICT_STUBS
  • 通过 when() 打桩的方法,只要调用了就会被自动校验
  • 没有打桩的方法,必须手动校验
  • 打桩方法,没有被调用,相当于没有校验
@Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
List list = mock(List.class);
when(list.get(0)).thenReturn("foo");
list.size();
verify(list).size();
list.get(0); // Automatically verified by STRICT_STUBS
verifyNoMoreInteractions(list); // No need of ignoreStubs()

4 Argument matchers

  • 通过 matchers 模糊指定打桩方法的参数
  • 参数匹配器允许灵活的 verify 或 stubbing
  • 参数匹配器相关文档:MockitoHamcrestArgumentMatchers
  • 只要方法中的一个参数使用 matchers 其余方法必须也都使用 matchers
//stubbing using built-in anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");

//stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
//isValid 效果同下面的 lambda 表达式
when(mockedList.contains(argThat(isValid()))).thenReturn(true);

//following prints "element"
System.out.println(mockedList.get(999));

//you can also verify using an argument matcher
verify(mockedList).get(anyInt());

//argument matchers can also be written as Java 8 Lambdas
verify(mockedList).add(argThat(someString -> someString.length() > 5));

//以下是正确的
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//以下是错误的
verify(mock).someMethod(anyInt(), anyString(), "third argument");

4.1 自定义 matcher

class ListOfTwoElements implements ArgumentMatcher<List> {
    public boolean matches(List list) {
        return list.size() == 2;
    }
    public String toString() {
        //printed in verification errors
        return "[list of 2 elements]";
    }
}
List mock = mock(List.class);
when(mock.addAll(argThat(new ListOfTwoElements))).thenReturn(true);
//Arrays.asList("one","two") 满足 matches 所以能被使用
mock.addAll(Arrays.asList("one", "two"));
verify(mock).addAll(argThat(new ListOfTwoElements()));

//简写
verify(mock).addAll(argThat(new ListOfTwoElements()));
//becomes,将 argThat(new ListOfTwoElements()) 抽成方法
verify(mock).addAll(listOfTwoElements());

//lambda
verify(mock).addAll(argThat(list -> list.size() == 2));

5 ArgumentCaptor 获取执行方法时的参数

List list = mock(ArrayList.class);
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
list.add(1);
int temp = ThreadLocalRandom.current().nextInt(1000);
list.add(temp);
//argument 只有 verify 之后才有值
verify(list, times(2)).add(argument.capture());
//getValue 是最后一次的参数值
assert argument.getValue() == temp;
//getAllValues() 包含所有调用的参数值
assert argument.getAllValues().contains(temp);
assert argument.getAllValues().contains(1);

6 自定义 mock 返回的默认值/doAnswer

6.1 自定义默认返回值

List list = mock(ArrayList.class, new ArrayListAnswer());
assert "[1, 2]".equals(list.get(1).toString());
verify(list).get(anyInt());
//lambda 形式
List list1 = mock(ArrayList.class, invocation -> Arrays.asList(1, 3));
assert "[1,3]".equals(list1.get(1).toString());
verify(list).get(anyInt());

class ArrayListAnswer implements Answer<List> {
    @Override
    public List answer(InvocationOnMock invocation) throws Throwable {
        return Arrays.asList(1, 2);
    }
}

6.2 doAnswer

List list = mock(ArrayList.class);
doAnswer(invocation -> 11).when(list).get(1);
assert 11 == (int)list.get(1);
assert list.get(2) == null;

7 MockingDetails 获取对象信息

//To identify whether a particular object is a mock or a spy:
Mockito.mockingDetails(someObject).isMock();
Mockito.mockingDetails(someObject).isSpy();

//Getting details like type to mock or default answer:
MockingDetails details = mockingDetails(mock);
details.getMockCreationSettings().getTypeToMock();
details.getMockCreationSettings().getDefaultAnswer();

//Getting invocations and stubbings of the mock:
MockingDetails details = mockingDetails(mock);
details.getInvocations();
details.getStubbings();

//Printing all interactions (including stubbing, unused stubs)
System.out.println(mockingDetails(mock).printInvocations());

8 代理难以 mock 的对象

  • Final classes but with an interface
  • Already custom proxied object
  • Special objects with a finalize method, i.e. to avoid executing it 2 times
//本来是打算 mock 此对象的,但是是 final
DontYouDareToMockMe dare = new DontYouDareToMockMe();
List<Integer> mock = mock(List.class, delegatesTo(dare));
//mock DontYouDareToMockMe 的 get() 方法!!
doReturn(2).when(mock).get(anyInt());
//只要方法名和参数一样就可以
assert mock.get(1) == 2;
assert !systemOutRule.getLog().contains("----------->调用了 final 类的方法");
final class DontYouDareToMockMe {
    public Integer get(int idx) { return idx;}
}

参考

mockito 官方文档
mockito 测试github地址

### Mockito 工作原理深入解析 Mockito 是一款强大的 Java 单元测试框架,主要用于创建和管理 Mock 对象。它的核心理念是通过模拟依赖来隔离被测代码的行为,从而使单元测试更加专注、可靠且易于维护。以下是对其工作原理的详细分析: #### 1. 创建 Mock 对象 Mockito 利用动态代理技术(基于 JDK 动态代理或 CGLIB)生成 Mock 对象。对于接口类型的对象,Mockito 默认使用 JDK 动态代理;而对于非接口类型,则采用 CGLIB 实现字节码增强[^2]。 - **JDK 动态代理**:通过 `java.lang.reflect.Proxy` 类实现,仅能代理实现了某些接口的对象。 - **CGLIB 字节码增强**:通过对目标类生成子类的方式实现代理功能,适用于未实现接口的情况。 ```java // 使用 Mockito.mock() 方法创建 Mock 对象 Database mockDb = Mockito.mock(Database.class); ``` 在此过程中,Mockito 自动生成了一个新的类,并覆盖了原类的所有方法以拦截调用[^1]。 --- #### 2. 行为定义与交互控制 Mockito 提供了一系列 API 来定义 Mock 对象的行为以及验证其交互情况。主要涉及以下几方面: ##### (1) 定义返回值 通过 `when(...).thenReturn(...)` 或其他变体方法设定特定输入对应的输出结果。 ```java when(mockDb.query("SELECT * FROM users")).thenReturn(Arrays.asList(new User("Alice"), new User("Bob"))); List<User> result = mockDb.query("SELECT * FROM users"); assertEquals(2, result.size()); ``` 此处,Mockito 记录下对 `mockDb.query()` 方法的调用参数及其关联的结果映射表[^1]。 ##### (2) 抛出异常 可以配置 Mock 方法在满足一定条件下抛出指定异常。 ```java doThrow(new RuntimeException("Error occurred!")) .when(mockDb) .update(anyString()); mockDb.update("UPDATE users SET active=true WHERE id=1"); // 将触发异常 ``` 此类行为同样存储于内部状态机中,在后续匹配到对应调用时生效。 --- #### 3. 验证交互逻辑 除了伪造响应外,Mockito 还允许开发者检查实际发生的调用序列是否符合预期。 ##### (1) 基本验证 利用 `verify(...)` 方法确认某方法确实被执行过若干次或者根本未曾调用。 ```java mockDb.insert("INSERT INTO logs VALUES ('info', 'System started')"); verify(mockDb, times(1)).insert(eq("INSERT INTO logs VALUES ('info', 'System started')")); ``` 上述语句表明期望 `insert` 方法恰好接收给定 SQL 参数一次[^1]。 ##### (2) 复杂场景支持 针对更复杂的情形,比如异步操作或多线程环境下,可借助 Argument Captor 获取传参详情进而实施进一步校验。 ```java ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); verify(mockDb).execute(captor.capture()); assertThat(captor.getValue(), containsString("DELETE")); ``` 这里展示了如何捕获传递至 `execute` 方法的实际字符串内容并加以断言。 --- #### 4. 底层实现剖析 为了达成前述特性,Mockito 主要依靠两项关键技术——反射与字节码操控。 - **反射机制**:用于访问私有字段/方法、修改访问权限等基本任务。例如初始化 InvocationHandler 实例或将自动生成的代理类加载进 JVM 中运行[^2]。 - **字节码生成库**:如 ByteBuddy(取代早期使用的 Objenesis+CGLIB 组合),负责实时构建扩展版的目标类副本,同时注入额外的功能片段[^2]。 典型例子如下所示: ```java public class $MockitoGeneratedSubclass extends RealService { private final MethodInterceptor interceptor; public $MockitoGeneratedSubclass(MethodInterceptor interceptor) { this.interceptor = interceptor; } @Override public String processRequest(String request) { return interceptor.apply(super.processRequest(request)); } } ``` 可以看到,最终产物是一个继承原始服务类的新实体,其中每个公开成员函数都被重新定向到了预设处理器处处理。 --- #### 总结 综上所述,Mockito 的运作依托于先进的代理技术和灵活易懂的操作界面,极大地简化了面向对象系统的单元测试流程。与此同时,它也充分体现了软件工程领域关于松散耦合的设计原则,推动了高质量编码文化的传播与发展[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值