← 返回题目列表

Mockito 的 verify 怎么验证方法调用?ArgumentCaptor 和参数匹配器怎么用?

高频 中等 第 12 / 23 题 更新于 2026/08/03
MockitoverifyArgumentCaptor参数匹配器

简化版

除了「打桩(stubbing,when(...).thenReturn(...) 设定 mock 的返回值)」,Mockito 还有一大能力是「验证交互(verify)」——检查「某个 mock 的方法被调用了没、调用了几次、用什么参数调的」。核心:verify(mock).方法(参数)——验证这个方法被调用了(默认 1 次);② 验证次数——verify(mock, times(2))(2 次)、never()(没调用)、atLeast(n)/atMost(n)(至少/至多);③ 参数匹配器(argument matcher)——验证/打桩时不写死参数,用 any()eq(x)anyString()argThat(条件) 等灵活匹配(如 verify(mock).save(any(User.class)) 验证「用任意 User 调过 save」);ArgumentCaptor(参数捕获器)——「捕获」方法被调用时实际传入的参数,然后对捕获到的参数做断言(比 matcher 更精细,能拿到实际对象校验它的字段)。为什么需要 verify:有些方法没有返回值(如 savesendlog),没法用返回值断言,只能验证「它被以正确的参数调用了」(交互验证)。核心:verify 验证方法调用(次数、参数),参数匹配器灵活匹配参数,ArgumentCaptor 捕获实际参数做精细断言。

详细版

verify 和参数处理

工具作用
verify(mock).method()验证调用(默认 1 次)
verify(mock, times(n))验证调用 n 次
verify(mock, never())验证从未调用
verify(mock, atLeast/atMost(n))至少/至多 n 次
any()/eq()/anyString()参数匹配器(灵活匹配)
argThat(条件)自定义参数匹配
ArgumentCaptor捕获实际参数做断言
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    @Mock UserRepository userRepo;
    @Mock EmailService emailService;
    @InjectMocks OrderService orderService;

    @Test void createOrder() {
        // 打桩
        when(userRepo.findById(1L)).thenReturn(new User("Tom"));
        // 执行
        orderService.create(1L);

        // ① verify:验证 save 被调用了 1 次
        verify(userRepo).save(any(Order.class));
        // ② 验证次数
        verify(emailService, times(1)).send(anyString(), anyString());
        verify(emailService, never()).sendSms(anyString());   // 没调用短信

        // ③ 参数匹配器 + 精确
        verify(userRepo).save(argThat(order -> order.getUserId() == 1L));

        // ④ ArgumentCaptor:捕获实际参数做断言
        ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class);
        verify(userRepo).save(captor.capture());
        Order savedOrder = captor.getValue();          // 拿到实际保存的 Order
        assertEquals(1L, savedOrder.getUserId());      // 断言它的字段
        assertEquals("PENDING", savedOrder.getStatus());
    }
}

⚠️ 两个关键点:「没有返回值的方法只能用 verify 验证」和「ArgumentCaptor 比 matcher 更适合校验复杂参数」为什么需要 verify:有返回值的方法可以用「打桩 + 断言返回值」测试(when().thenReturn() + assertEquals);但没有返回值的方法(void save()void send()void log())没法用返回值断言——你怎么知道 save 被正确调用了?只能用 verify 验证「它被以什么参数、调用了几次」(交互验证)。ArgumentCaptor vs 参数匹配器argThat(条件) 能在 verify 时校验参数是否满足条件(内联),但如果参数是复杂对象、要校验多个字段ArgumentCaptor 更清晰——它「捕获」方法实际被调用时传入的参数对象,然后你可以像普通对象一样对它做多个断言(captor.getValue().getXxx()),失败信息也更清晰(能看到实际值)。参数匹配器的坑:一旦用了 matcher,所有参数都要用 matcher(不能一个用 eq(1)、一个直接写 "abc"),要么全 matcher(eq("abc"))、要么全不用;混用会报错。

完整版教学

一、Mockito 的两大能力:打桩与验证

先理解 Mockito 的两大能力:

Mockito 的两大能力:
  ① 打桩(Stubbing):设定 mock 的行为(返回值)
     when(mock.method(参数)).thenReturn(值)
     → 让 mock 返回你要的值(控制测试的输入/依赖)
  ② 验证(Verify):检查 mock 的交互
     verify(mock).method(参数)
     → 检查方法被调用了没、几次、什么参数(验证行为)

两者的角色:
  打桩:控制"依赖返回什么"(给被测代码喂数据)
  验证:检查"被测代码怎么用依赖"(验证交互是否正确)

例(下单服务):
  打桩:when(userRepo.findById(1)).thenReturn(user)
    → 让 userRepo 返回一个用户(控制依赖)
  验证:verify(userRepo).save(any())
    → 检查 orderService 调了 save(验证行为)

为什么两者都需要:
  打桩:让被测代码在"可控的依赖行为"下运行
  验证:确认被测代码"正确地调用了依赖"(尤其无返回值的)

所以 Mockito 打桩(设返回值)+验证(检查交互),两大能力

Mockito 的两大能力:① 打桩(Stubbing)设定 mock 行为when(mock.method()).thenReturn(值) 让 mock 返回要的值、控制测试输入/依赖)、② 验证(Verify)检查 mock 交互verify(mock).method() 检查方法被调用了没/几次/什么参数)。角色:打桩控制依赖返回什么、验证检查被测代码怎么用依赖。为什么都需要:打桩让被测代码在可控依赖下运行、验证确认正确调用依赖(尤其无返回值的)。理解「Mockito 两大能力:打桩(when.thenReturn 设返回值控制依赖)+验证(verify 检查调用没/几次/什么参数);打桩控制依赖返回、验证检查被测代码怎么用依赖」,就理解了 Mockito 的两大能力。

二、verify:验证方法调用

verify——验证方法被调用:

verify:验证 mock 的方法被调用
  verify(mock).method(参数)
  → 验证这个方法被调用了(默认恰好 1 次)
  → 没调用/参数不对 → 验证失败

验证次数:
  verify(mock, times(n)):恰好 n 次
  verify(mock, never()):从未调用(0 次)
  verify(mock, atLeastOnce()):至少 1 次
  verify(mock, atLeast(n)):至少 n 次
  verify(mock, atMost(n)):至多 n 次
  verify(mock, times(0)):0 次(同 never)

例:
  verify(userRepo).save(order);              // save 被调 1 次
  verify(emailService, times(2)).send(...);  // send 被调 2 次
  verify(smsService, never()).send(...);     // sms 从未调用

其他验证:
  verifyNoInteractions(mock):mock 没有任何交互
  verifyNoMoreInteractions(mock):验证后没有更多交互
  InOrder:验证调用顺序
    InOrder inOrder = inOrder(mock1, mock2);
    inOrder.verify(mock1).a();
    inOrder.verify(mock2).b();   // 验证 a 在 b 之前调

所以 verify 验证方法调用(次数:times/never/atLeast/atMost)

verify 验证方法被调用——verify(mock).method(参数)(验证被调用,默认恰好 1 次,没调用/参数不对失败)。验证次数times(n)(恰好 n 次)、never()(0 次)、atLeastOnce()/atLeast(n)/atMost(n)。其他:verifyNoInteractions(没任何交互)、verifyNoMoreInteractions(没更多交互)、InOrder(验证调用顺序)。理解「verify 验证方法调用(默认 1 次,没调用/参数不对失败);次数 times(n)/never/atLeastOnce/atLeast/atMost;其他 verifyNoInteractions/InOrder 验证顺序」,就掌握了 verify。

三、为什么需要 verify

理解「为什么需要 verify」——无返回值的方法:

有返回值的方法:用返回值断言就行
  int result = calc.add(2, 3);
  assertEquals(5, result);   // 用返回值断言
  → 不需要 verify

无返回值的方法(void):没返回值,怎么测?
  orderService.create(1L);   // create 内部会调 save、send(都无返回值)
  → 怎么验证 create 做对了?
  → 它调了 userRepo.save(order)、emailService.send(...) 吗?

  用 verify 验证交互:
    verify(userRepo).save(any());        // 验证调了 save
    verify(emailService).send(...);      // 验证调了 send
  → 通过"验证依赖被正确调用"来测试无返回值的方法

典型的无返回值方法:
  save/update/delete(持久化)
  send/publish(发送消息、邮件)
  log/record(日志、记录)
  → 这些"做了什么"没法用返回值看,只能 verify

verify 验证的是"交互/行为":
  不是验证"返回了什么"(值)
  而是验证"调用了什么依赖、怎么调的"(交互)
  → 行为验证(Behavior Verification)

所以 verify 用于验证无返回值方法(交互验证,save/send/log)

「为什么需要 verify」——有返回值的方法用返回值断言assertEquals(5, result) 不需要 verify)。无返回值的方法(void)没返回值怎么测——orderService.create(1L) 内部调 save、send(都无返回值),怎么验证做对了?用 verify 验证交互verify(userRepo).save(any()) 验证调了 save)。典型无返回值方法:save/update/delete(持久化)、send/publish(发消息邮件)、log(日志)。verify 验证的是交互/行为(调用了什么依赖、怎么调的)而非返回值。理解「有返回值用返回值断言不需 verify、无返回值方法(save/send/log)没返回值只能 verify 验证交互(验证调了什么依赖怎么调的);verify 是行为验证不是值验证」,就理解了为什么需要 verify。

四、参数匹配器

参数匹配器(argument matcher)——灵活匹配参数:

参数匹配器:打桩/验证时不写死参数,灵活匹配
  常用匹配器:
    any():任意参数(包括 null)
    any(Xxx.class):任意某类型
    anyString()/anyInt()/anyLong():任意某基本类型/字符串
    eq(value):等于某值(用 matcher 时表示精确等于)
    isNull()/notNull():null/非 null
    contains("x")/startsWith("x"):字符串匹配
    argThat(条件):自定义条件(Lambda)

用在打桩:
  when(userRepo.findById(anyLong())).thenReturn(user);
  → 任意 id 都返回这个 user

用在验证:
  verify(userRepo).save(any(Order.class));   // 用任意 Order 调过 save
  verify(emailService).send(eq("tom@x.com"), anyString());  // 收件人精确、内容任意

argThat(自定义匹配):
  verify(userRepo).save(argThat(order ->
    order.getUserId() == 1L && order.getStatus().equals("PENDING")));
  → 验证 save 的参数满足这个条件

★ 匹配器的坑(不能混用):
  用了 matcher,所有参数都要用 matcher
  ✗ verify(mock).send(eq("a"), "b")   // 一个 matcher 一个字面量 → 报错
  ✓ verify(mock).send(eq("a"), eq("b"))  // 都用 matcher
  ✓ verify(mock).send("a", "b")          // 都不用
  → 要么全 matcher、要么全字面量

所以参数匹配器灵活匹配参数(any/eq/anyString/argThat),不能混用

参数匹配器 灵活匹配参数——常用:any()(任意,含 null)、any(Xxx.class)anyString/anyInteq(value)(精确等于)、isNull/notNullcontains/startsWithargThat(条件)(自定义 Lambda)。用在打桩(when(findById(anyLong())).thenReturn(user))和验证(verify().save(any(Order.class)))。argThat 自定义匹配(校验参数满足条件)。匹配器的坑(不能混用)用了 matcher 所有参数都要用 matchersend(eq("a"), "b") 报错、要 send(eq("a"), eq("b"))send("a","b"))。理解「参数匹配器灵活匹配:any/any(类)/anyString/eq(精确)/argThat(自定义);用在打桩和验证;★不能混用(用了 matcher 所有参数都要 matcher)」,就掌握了参数匹配器。

五、ArgumentCaptor:捕获参数

ArgumentCaptor——捕获实际参数做精细断言:

ArgumentCaptor(参数捕获器):
  "捕获"方法实际被调用时传入的参数,然后对它做断言

用法:
  ① 创建 Captor:
     ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class);
  ② verify 时用 captor.capture() 捕获:
     verify(userRepo).save(captor.capture());
  ③ 拿到捕获的参数、断言:
     Order savedOrder = captor.getValue();
     assertEquals(1L, savedOrder.getUserId());
     assertEquals("PENDING", savedOrder.getStatus());
  → 拿到实际保存的 Order 对象,校验它的多个字段

多次调用(捕获多个):
  captor.getAllValues():所有捕获的值(List)

ArgumentCaptor vs argThat(参数匹配器):
  argThat(条件):在 verify 时内联校验参数满足条件
    verify(mock).save(argThat(o -> o.getUserId() == 1L))
    → 简单条件、内联
  ArgumentCaptor:捕获参数,之后做多个断言
    → 复杂对象、要校验多个字段、失败信息清晰

什么时候用 ArgumentCaptor:
  ① 参数是复杂对象、要校验多个字段
  ② 想对参数做详细断言(比 argThat 清晰)
  ③ 失败时想看到实际传入的值(更好定位)

@Captor 注解(简化创建):
  @Captor ArgumentCaptor<Order> captor;
  → 不用手动 forClass

所以 ArgumentCaptor 捕获实际参数做精细断言(复杂对象、多字段校验)

ArgumentCaptor 捕获实际参数做精细断言——① 创建 Captor(forClass)→ ② verify 时 captor.capture() 捕获 → ③ captor.getValue() 拿到参数做断言(拿到实际保存的对象校验多个字段)。多次调用用 getAllValues()ArgumentCaptor vs argThatargThat 内联校验简单条件、ArgumentCaptor 捕获后做多个断言(复杂对象、多字段校验、失败信息清晰)。什么时候用 ArgumentCaptor:参数是复杂对象要校验多个字段、想详细断言、失败想看实际值@Captor 注解简化创建。理解「ArgumentCaptor 捕获实际参数做断言:forClass 创建→capture()捕获→getValue()拿参数断言(校验多字段);vs argThat:argThat 内联简单条件、Captor 复杂对象多字段清晰;@Captor 简化」,就掌握了 ArgumentCaptor。

六、实践与总结

总结 verify 和参数处理的实践:

实践建议:
  ① 无返回值方法用 verify 验证交互(save/send/log)
  ② 有返回值方法用返回值断言(不用 verify)
  ③ 验证次数(times/never/atLeast)确认调用符合预期
  ④ 参数不关心用 any()、关心用 eq()、复杂用 ArgumentCaptor
  ⑤ 校验复杂对象的多个字段用 ArgumentCaptor(比 argThat 清晰)

常见验证场景:
  ① 验证依赖被正确调用(save 被调、用对的参数)
  ② 验证没有多余调用(never——不该调的没调)
  ③ 验证调用次数(times——调了正确的次数)
  ④ 验证参数正确(ArgumentCaptor——传入的对象字段对)
  ⑤ 验证调用顺序(InOrder)

注意点:
  ① 参数匹配器不能混用(要么全 matcher、要么全字面量)
  ② 别过度验证(验证关键交互即可,别验证所有细节 → 脆弱)
  ③ verify 验证交互、断言验证状态/值 → 结合用

核心总结:
  verify 验证方法调用(次数、参数)——尤其无返回值方法
  参数匹配器(any/eq/anyString/argThat)灵活匹配参数
  ArgumentCaptor 捕获实际参数做精细断言(复杂对象、多字段)
  打桩(when.thenReturn)控制依赖 + 验证(verify)检查交互

verify 和参数处理的实践:无返回值方法用 verify、有返回值用返回值断言、验证次数确认调用、参数不关心用 any 关心用 eq 复杂用 ArgumentCaptor、复杂对象多字段用 ArgumentCaptor。场景:验证依赖被正确调用、验证没多余调用(never)、验证次数(times)、验证参数(Captor)、验证顺序(InOrder)。注意:参数匹配器不能混用、别过度验证(脆弱)、verify 验证交互+断言验证状态结合。理解「实践:无返回值用 verify/有返回值用断言/验证次数/参数 any-eq-Captor/复杂对象用 Captor;场景验证调用/没多余调用/次数/参数/顺序;注意匹配器不混用+别过度验证」,就掌握了实践与总结。

记忆钩子:「Mockito 两大能力:打桩(when.thenReturn 设返回值控制依赖)+★验证(verify 检查交互);verify(mock).method(参数)验证调用(默认 1 次);次数:times(n)/never/atLeastOnce/atLeast/atMost;★为什么需要 verify:无返回值方法(save/send/log)没法用返回值断言,只能验证被以什么参数调了几次(交互验证);参数匹配器灵活匹配:any()/any(类)/anyString/eq(精确)/argThat(自定义 Lambda),★不能混用(用了 matcher 所有参数都要 matcher);ArgumentCaptor 捕获实际参数做精细断言:forClass 创建→capture()→getValue()拿参数校验多字段(复杂对象比 argThat 清晰),@Captor 简化;InOrder 验证调用顺序;别过度验证(脆弱)」

七、常见误区与追问

  • 误区:无返回值的方法没法测试。 能测——用 verify 验证交互:虽然没有返回值可断言,但可以验证「它被以正确的参数调用了、调用了正确的次数」;比如 orderService.create() 内部调 userRepo.save(),就 verify(userRepo).save(argThat(…)) 验证 save 被以正确的 Order 调用;这是行为验证。
  • 误区:参数匹配器可以和字面量混用。 不能——一旦某个参数用了 matcher(如 eq、any),该方法的所有参数都要用 matcher;verify(mock).send(eq(“a”), “b”) 会报错(一个 matcher 一个字面量);要么全用 matcher(send(eq(“a”), eq(“b”)))、要么全用字面量(send(“a”, “b”))。
  • 误区:验证复杂参数用 argThat 就够了。 argThat 适合简单条件的内联校验;但如果参数是复杂对象、要校验多个字段,用 ArgumentCaptor 更清晰——它捕获方法实际被调用时传入的参数对象,然后你可以像普通对象一样对它做多个断言(captor.getValue().getXxx()),失败信息也能看到实际值,更好定位。
  • 误区:verify 和 when(打桩)是一回事。 不同——when(…).thenReturn(…) 是打桩(设定 mock 被调用时返回什么,控制依赖的行为,测试前设置);verify(mock).method() 是验证(检查 mock 的方法被调用了没、几次、什么参数,测试后检查交互);打桩控制输入、验证检查行为。
  • 追问:Mockito 的 verify 是干什么的,什么时候用? verify 用于验证 mock 的方法被调用的情况——检查某个方法被调用了没、调用了几次(times/never/atLeast/atMost)、用什么参数调的;主要用于测试没有返回值的方法(void,如 save、send、log):这些方法没法用返回值断言,只能通过 verify 验证「它被以正确的参数、正确的次数调用了」(交互/行为验证);比如验证下单服务确实调用了 userRepo.save() 和 emailService.send()。
  • 追问:ArgumentCaptor 和参数匹配器 argThat 有什么区别? argThat(条件) 在 verify 时内联校验参数是否满足条件(用 Lambda),适合简单条件;ArgumentCaptor 是「捕获」方法实际被调用时传入的参数对象,之后可以像普通对象一样对它做多个断言(captor.getValue()),适合参数是复杂对象、要校验多个字段的场景;ArgumentCaptor 的失败信息更清晰(能看到实际传入的值);简单条件用 argThat、复杂对象多字段校验用 ArgumentCaptor。
  • 追问:怎么用 ArgumentCaptor 校验保存的对象的字段? ① 创建 captor:ArgumentCaptor captor = ArgumentCaptor.forClass(Order.class)(或用 @Captor 注解);② 在 verify 时用 captor.capture() 捕获参数:verify(userRepo).save(captor.capture());③ 用 captor.getValue() 拿到实际被保存的 Order 对象,然后对它做断言:assertEquals(1L, captor.getValue().getUserId())、assertEquals(“PENDING”, captor.getValue().getStatus());如果方法被调用多次,用 captor.getAllValues() 拿到所有捕获的值(List)。

八、加强记忆

Mockito 除了「打桩(stubbing,when(...).thenReturn(...) 设 mock 返回值)」,还有「验证交互(verify)」——检查某个 mock 的方法被调用了没、几次、用什么参数调的verify(mock).method(参数) 验证方法被调用(默认 1 次);② 验证次数times(n)(恰好 n 次)、never()(0 次)、atLeast(n)/atMost(n)③ 参数匹配器any()(任意)、any(Xxx.class)anyString()eq(value)(精确)、argThat(条件)(自定义 Lambda)——一旦用 matcher,所有参数都要用 matcher(不能混用)ArgumentCaptor(参数捕获器)forClass 创建 → capture() 捕获 → getValue() 拿到实际传入的参数对象做多个断言(复杂对象、多字段校验,比 argThat 清晰、失败信息能看实际值@Captor 简化)。为什么需要 verify没有返回值的方法(save/send/log)没法用返回值断言,只能验证它被以正确的参数、正确的次数调用了(交互/行为验证)。打桩控制依赖返回什么、验证检查被测代码怎么用依赖。注意别过度验证(脆弱)。一句话「Mockito verify 验证方法调用(次数 times/never/atLeast、参数);为什么需要:无返回值方法(save/send/log)没法用返回值断言只能验证交互;参数匹配器 any/eq/anyString/argThat 灵活匹配(不能混用);ArgumentCaptor 捕获实际参数做精细断言(复杂对象多字段,比 argThat 清晰);打桩控制依赖+验证检查交互」。