【问题标题】:Test RabbitTemplate#convertAndSend as lambda with Mockito & JUnit使用 Mockito 和 JUnit 将 RabbitTemplate#convertAndSend 测试为 lambda
【发布时间】:2026-02-15 03:40:01
【问题描述】:

我正在尝试测试编写为 lambda 的 RabbitTemplate#convertAndSend 方法,如下所示:

// other stuff omitted for brevity

        rabbitTemplate.convertAndSend(myQueue, jsonString, message -> {
        message.getMessageProperties().setPriority(priority);
        return message;
        });

// other stuff omitted for brevity

我正在尝试做的测试用例是使用ArgumentCaptor 以验证是否使用正确的参数调用了该方法。

@Test
public void givenMyNotification_whenElementIsSent_thenSetPriorityAndSendValidParameters() {

final ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
final int expectedPriority = 5;
final Notification expected = TestUtils.getNotification();

testClass.handleNotification(expected);

verify(rabbitTemplate).convertAndSend(captor.capture(), captor.capture(),
    ArgumentMatchers.eq(MessagePostProcessor.class));

// assertThat...
));

}

在验证步骤测试失败,因为参数不同。

Wanted:
<Capturing argument>,
<Capturing argument>,
interface org.springframework.amqp.core.MessagePostProcessor

Actual invocation:
"myQueue",
"myJson",
com.example.notification.service.NotificationService$$Lambda$5/73698537@5bda80bf

我尝试了其他几个来自 mockito 和 hamcrest 的 Matcher,但都无济于事。

所以,我的问题是:

  1. 如何测试这种东西?

  2. 这是一个好的做法还是有其他/更好的方法来测试兔子模板发送?

【问题讨论】:

    标签: java spring-boot junit rabbitmq mockito


    【解决方案1】:

    除了最后一个匹配器 ArgumentMatchers.eq(MessagePostProcessor.class) 之外,您几乎可以理解。

    您实际上要求 Mockitoequatility 与参数的类匹配。 你更应该匹配:

    • 使用ArgumentMatchers.any(MessagePostProcessor.class)的参数类型
    • 或实际值ArgumentMatchers.eq(expectedMessageProcessor),如果你碰巧有的话
    • 如果确实需要检查参数的值,也可以使用参数捕获器

    然而,在这种特殊情况下,如果您使用第一个选项,您可能会遇到编译器问题,因为 RabbitTemplate 类有两个类似的方法:

    • convertAndSend(字符串、字符串、对象)
    • convertAndSend(String, Object, MessagePostProcessor)

    要解决这个问题,你可以像这样强制第二个参数的类型为对象:

    Mockito.verify(rabbitTemplate).convertAndSend(captor.capture(), (Object) captor.capture(),
                Mockito.any(MessagePostProcessor.class));
    

    或者更好的是,为两个不同的参数设置两个不同的ArgumentCaptors:

    ArgumentCaptor<String> routingKeyCaptor = ArgumentCaptor.forClass(String.class);
    ArgumentCaptor<Object> messageCaptor = ArgumentCaptor.forClass(Object.class);
    
    ...
    
    verify(rabbitTemplate).convertAndSend(routingKeyCaptor.capture(), messageCaptor.capture(), any(MessagePostProcessor.class));
    

    希望这会有所帮助!

    【讨论】:

      【解决方案2】:

      我认为这完全取决于您真正要测试什么以及它增加了什么商业价值。

      1. 您想测试RabbitTemplate 本身吗? Spring 存储库对此进行了测试,但恕我直言,您不需要这样做。
      2. 您是否在测试 Spring Service 并且必须验证消息是否已发送?
      3. 您想确保消息能够到达目的地吗?

      对于 (1),Spring Frameworkhere in their unit tests for the RabbitTemplate 中有一些您正在编写的测试类型的示例。

      例如验证底层ConnectionFactory实际上是created a channel

          txTemplate.execute(status -> {
              template.convertAndSend("foo", "bar");
              return null;
          });
          txTemplate.execute(status -> {
              template.convertAndSend("baz", "qux");
              return null;
          });
          verify(mockConnectionFactory, Mockito.times(1)).newConnection(any(ExecutorService.class), anyString());
          // ensure we used the same channel
          verify(mockConnection, times(1)).createChannel();
      

      对于上面的 (2),您不需要使用参数捕获器等。您可以将模拟 RabbitTemplate 注入到服务中,我个人喜欢针对我调用该方法的参数进行测试或使用Mockito#any

      仅供参考,就断言而言,我还想看看AssertJ,它有一个很好的 Spring 批准印章,而且它的流畅 API(尤其是智能感知)使它非常易于使用.

      对于 (3),我强烈推荐 https://www.testcontainers.org/(如果您使用的是 docker),我还没有检查过,但可能有一个 JUnit @Rule 用于创建 RabbitMQ 代理(I'必须检查这一点)。我为 ActiveMQ Artemis 做了类似的事情,它很快就会为你启动一个代理,你可以使用排队的消息。

      【讨论】:

      • 因为这些只是单元测试,我绝对不想使用测试容器。不,我没有测试RabbitTemplate,spring 开发人员已经这样做了。我想测试消息是否与预期的参数一起发送,所以any() 不是一个选项。