如何为含ServiceBusReceivedMessageContext参数的processMessage方法编写测试
如何为依赖第三方final类的方法编写测试用例?
问题场景
需要为如下processMessage方法编写测试用例,该方法的参数ServiceBusReceivedMessageContext是第三方Azure Service Bus SDK中的final类,且无法使用Mockito等 mocking 工具。
待测试方法代码
public void processMessage(ServiceBusReceivedMessageContext context) { try { ServiceBusReceivedMessage message = context.getMessage(); } catch (Exception e) { log.error("Unhandled exception occurred while processing message: {}", e.getMessage(), e); //throw new RuntimeException("Error processing message", e); } }
第三方类ServiceBusReceivedMessageContext源码
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.azure.messaging.servicebus; import com.azure.messaging.servicebus.models.AbandonOptions; import com.azure.messaging.servicebus.models.CompleteOptions; import com.azure.messaging.servicebus.models.DeadLetterOptions; import com.azure.messaging.servicebus.models.DeferOptions; import java.util.Objects; /** * The Service Bus processor message context that holds a received message and additional methods to settle the message. */ public final class ServiceBusReceivedMessageContext { private final ServiceBusMessageContext receivedMessageContext; private final ServiceBusReceiverAsyncClient receiverClient; private final String fullyQualifiedNamespace; private final String entityPath; ServiceBusReceivedMessageContext(ServiceBusReceiverAsyncClient receiverClient, ServiceBusMessageContext receivedMessageContext) { this.receivedMessageContext = Objects.requireNonNull(receivedMessageContext, "'receivedMessageContext' cannot be null"); this.receiverClient = Objects.requireNonNull(receiverClient, "'receiverClient' cannot be null"); entityPath = receiverClient.getEntityPath(); fullyQualifiedNamespace = receiverClient.getFullyQualifiedNamespace(); } /** * Gets the message received from Service Bus. * * @return The message received from Service Bus. */ public ServiceBusReceivedMessage getMessage() { return receivedMessageContext.getMessage(); } /** * Gets the Service Bus resource this instance of {@link ServiceBusProcessorClient} interacts with. * * @return The Service Bus resource this instance of {@link ServiceBusProcessorClient} interacts with. */ public String getEntityPath() { return this.entityPath; } /** * Gets the fully qualified Service Bus namespace that this instance of {@link ServiceBusProcessorClient} * is associated with. This is likely similar to {@code {yournamespace}.servicebus.windows.net}. * * @return The fully qualified Service Bus namespace that this instance of {@link ServiceBusProcessorClient} * is associated with. */ public String getFullyQualifiedNamespace() { return this.fullyQualifiedNamespace; } /** * Abandons the {@link #getMessage() message} in this context. */ public void abandon() { receiverClient.abandon(receivedMessageContext.getMessage()).block(); } /** * Abandons the {@link #getMessage() message} in this context. * * @param options Additional options for abandoning the message. */ public void abandon(AbandonOptions options) { receiverClient.abandon(receivedMessageContext.getMessage(), options).block(); } /** * Completes the {@link #getMessage() message} in this context. */ public void complete() { receiverClient.complete(receivedMessageContext.getMessage()).block(); } /** * Completes the {@link #getMessage() message} in this context. * * @param options Additional options for completing the message. * @throws NullPointerException if {@code options} are null. */ public void complete(CompleteOptions options) { receiverClient.complete(receivedMessageContext.getMessage(), options).block(); } /** * Defers the {@link #getMessage() message} in this context. */ public void defer() { receiverClient.defer(receivedMessageContext.getMessage()).block(); } /** * Defers the {@link #getMessage() message} in this context. * * @param options Additional options for deferring the message. * @throws NullPointerException if {@code options} are null. */ public void defer(DeferOptions options) { receiverClient.defer(receivedMessageContext.getMessage(), options).block(); } /** * Dead-letters the {@link #getMessage() message} in this context. */ public void deadLetter() { receiverClient.deadLetter(receivedMessageContext.getMessage()).block(); } /** * Dead-letters the {@link #getMessage() message} in this context. * * @param options Additional options for dead-lettering the message. * * @throws NullPointerException if {@code options} are null. */ public void deadLetter(DeadLetterOptions options) { receiverClient.deadLetter(receivedMessageContext.getMessage(), options).block(); } }
解决方案
方案1:反射构造实例
利用Java反射绕过访问限制,直接构造ServiceBusReceivedMessageContext及其依赖对象,无需修改原代码,适合快速编写测试。
import org.junit.Test; import com.azure.messaging.servicebus.ServiceBusReceivedMessage; import com.azure.messaging.servicebus.ServiceBusReceivedMessageContext; import com.azure.messaging.servicebus.ServiceBusMessageContext; import com.azure.messaging.servicebus.ServiceBusReceiverAsyncClient; import reactor.core.publisher.Mono; import java.lang.reflect.Constructor; public class YourServiceTest { @Test public void testProcessMessage() throws Exception { // 1. 构造测试用的ServiceBusReceivedMessage ServiceBusReceivedMessage testMessage = ServiceBusReceivedMessage.builder() .body("test-message-content".getBytes()) .build(); // 2. 反射构造ServiceBusMessageContext(包级构造函数) Constructor<ServiceBusMessageContext> messageContextConstructor = ServiceBusMessageContext.class.getDeclaredConstructor(ServiceBusReceivedMessage.class); messageContextConstructor.setAccessible(true); ServiceBusMessageContext messageContext = messageContextConstructor.newInstance(testMessage); // 3. 实现ServiceBusReceiverAsyncClient的模拟对象 ServiceBusReceiverAsyncClient mockReceiver = new ServiceBusReceiverAsyncClient() { @Override public String getEntityPath() { return "test-queue"; } @Override public String getFullyQualifiedNamespace() { return "test-namespace.servicebus.windows.net"; } // 仅实现测试中可能触发的方法,其余按需返回空Mono或抛出异常 @Override public Mono<Void> complete(ServiceBusReceivedMessage message) { return Mono.empty(); } // 其他方法如abandon、defer等同理实现 }; // 4. 反射构造ServiceBusReceivedMessageContext实例 Constructor<ServiceBusReceivedMessageContext> contextConstructor = ServiceBusReceivedMessageContext.class.getDeclaredConstructor( ServiceBusReceiverAsyncClient.class, ServiceBusMessageContext.class); contextConstructor.setAccessible(true); ServiceBusReceivedMessageContext testContext = contextConstructor.newInstance(mockReceiver, messageContext); // 5. 执行测试 YourService service = new YourService(); service.processMessage(testContext); // 6. 添加断言,比如验证日志输出、消息处理结果等 } }
方案2:封装依赖为自定义接口
重构原代码,将对第三方类的依赖封装为自定义接口,通过适配器类适配第三方实现,符合面向接口编程原则,测试更灵活。
步骤1:定义自定义接口
public interface MessageContext { ServiceBusReceivedMessage getMessage(); String getEntityPath(); String getFullyQualifiedNamespace(); // 如需使用其他方法,如complete、abandon等,可在此添加对应方法签名 }
步骤2:实现适配器类
public class ServiceBusMessageContextAdapter implements MessageContext { private final ServiceBusReceivedMessageContext delegate; public ServiceBusMessageContextAdapter(ServiceBusReceivedMessageContext delegate) { this.delegate = delegate; } @Override public ServiceBusReceivedMessage getMessage() { return delegate.getMessage(); } @Override public String getEntityPath() { return delegate.getEntityPath(); } @Override public String getFullyQualifiedNamespace() { return delegate.getFullyQualifiedNamespace(); } // 如需适配其他方法,添加对应实现即可 }
步骤3:修改原方法参数
public class YourService { public void processMessage(MessageContext context) { try { ServiceBusReceivedMessage message = context.getMessage(); // 原处理逻辑保持不变 } catch (Exception e) { log.error("Unhandled exception occurred while processing message: {}", e.getMessage(), e); } } }
步骤4:编写测试用例
import org.junit.Test; import com.azure.messaging.servicebus.ServiceBusReceivedMessage; public class YourServiceTest { @Test public void testProcessMessage() { // 构造测试消息 ServiceBusReceivedMessage testMessage = ServiceBusReceivedMessage.builder() .body("test-content".getBytes()) .build(); // 模拟自定义MessageContext接口 MessageContext mockContext = new MessageContext() { @Override public ServiceBusReceivedMessage getMessage() { return testMessage; } @Override public String getEntityPath() { return "test-queue"; } @Override public String getFullyQualifiedNamespace() { return "test-namespace"; } }; // 执行测试 YourService service = new YourService(); service.processMessage(mockContext); // 添加断言逻辑 } }
方案3:集成测试(使用真实服务/测试容器)
如果需要验证完整流程,可以使用Azure Service Bus的测试容器(如Testcontainers)或Azure提供的本地模拟服务,发送真实消息到测试队列,让处理器接收并处理,以此完成测试。这种方式更接近生产环境,但需要额外的测试资源支持。
内容的提问来源于stack exchange,提问作者Shubham Singh
相关产品推荐
相关产品推荐

