You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为含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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 02:57:00