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

如何Mock注入的MessageHandlerInterface实现类列表?解决BeanInstantiationException异常方案

你遇到的这个错误其实很好理解——Spring没办法实例化List这种接口类型的Bean,@SpyBean直接标注在List<MessageHandlerInterface>上肯定行不通,因为Spring根本不知道要实例化哪种List实现类。下面给你几种靠谱的解决办法,从简洁到灵活都有:

方法一:用@MockBean创建Handler实例,自动聚合到List

这是最符合Spring依赖注入逻辑的方式,Spring会自动把所有类型为MessageHandlerInterface的Bean(包括你Mock的实例)收集到List中,注入到Component里。

修改后的测试类代码如下:

@SpringJUnitConfig
@SpringBootTest
@ActiveProfiles("test")
class ComponentTest {
    @Autowired
    private Component component;

    // 直接MockHandler接口实例,Spring会自动把它加入到目标List中
    @MockBean
    private MessageHandlerInterface mockHandler;

    private static final String SUBJECT = "test-subject";

    @Test
    public void onMessage() throws ExecutionException, InterruptedException, TimeoutException {
        /*
         * Given
         */
        String testMessage = UUID.randomUUID().toString();
        CompletableFuture<String> completableFuture = new CompletableFuture<>();

        // 定义Mock的行为
        Mockito.when(mockHandler.getSubject()).thenReturn(SUBJECT);
        Mockito.doAnswer(invocation -> {
            Message message = invocation.getArgument(0);
            completableFuture.complete(new String(message.getData()));
            return null;
        }).when(mockHandler).handleMessage(Mockito.any(Message.class));

        // 手动触发初始化(如果@PostConstruct没自动执行的话)
        component.initInterfaces();

        /*
         * When
         */
        nc.publish(SUBJECT, testMessage.getBytes());

        /*
         * Then
         */
        String message = completableFuture.get(2, TimeUnit.SECONDS);
        Assertions.assertEquals(testMessage, message);
        Mockito.verify(mockHandler, Mockito.times(1)).handleMessage(Mockito.any(Message.class));
    }
}

如果需要多个Handler实例,只需要添加多个@MockBean private MessageHandlerInterface mockHandlerX;即可,Spring会自动把所有Mock实例注入到Component的List中。

方法二:手动创建List并注入到Component

如果你想完全控制List的内容,可以手动创建List实例并放入Mock对象,再通过反射注入到Component中:

@SpringJUnitConfig
@SpringBootTest
@ActiveProfiles("test")
class ComponentTest {
    @Autowired
    private Component component;

    private List<MessageHandlerInterface> mockHandlers;

    private static final String SUBJECT = "test-subject";

    @BeforeEach
    void setUp() throws NoSuchFieldException, IllegalAccessException {
        // 创建Mock Handler
        MessageHandlerInterface mockHandler = Mockito.mock(MessageHandlerInterface.class);
        Mockito.when(mockHandler.getSubject()).thenReturn(SUBJECT);

        mockHandlers = new ArrayList<>();
        mockHandlers.add(mockHandler);

        // 用反射把Mock List注入到Component
        Field field = Component.class.getDeclaredField("messageHandlerInterfaces");
        field.setAccessible(true);
        field.set(component, mockHandlers);

        // 重新初始化
        component.initInterfaces();
    }

    @Test
    public void onMessage() throws ExecutionException, InterruptedException, TimeoutException {
        /*
         * Given
         */
        String testMessage = UUID.randomUUID().toString();
        CompletableFuture<String> completableFuture = new CompletableFuture<>();

        Mockito.doAnswer(invocation -> {
            Message message = invocation.getArgument(0);
            completableFuture.complete(new String(message.getData()));
            return null;
        }).when(mockHandlers.get(0)).handleMessage(Mockito.any(Message.class));

        /*
         * When
         */
        nc.publish(SUBJECT, testMessage.getBytes());

        /*
         * Then
         */
        String message = completableFuture.get(2, TimeUnit.SECONDS);
        Assertions.assertEquals(testMessage, message);
    }
}

方法三:Spy具体的List实现类

如果你坚持要用@SpyBean,可以Spy一个具体的List实现(比如ArrayList),再在测试前添加Mock Handler:

@SpringJUnitConfig
@SpringBootTest
@ActiveProfiles("test")
class ComponentTest {
    @Autowired
    private Component component;

    // Spy一个具体的ArrayList实例,Spring可以正常实例化它
    @SpyBean
    private List<MessageHandlerInterface> messageHandlerInterfaces = new ArrayList<>();

    private static final String SUBJECT = "test-subject";

    @BeforeEach
    void setUp() {
        MessageHandlerInterface mockHandler = Mockito.mock(MessageHandlerInterface.class);
        Mockito.when(mockHandler.getSubject()).thenReturn(SUBJECT);
        messageHandlerInterfaces.add(mockHandler);

        // 重新初始化
        component.initInterfaces();
    }

    @Test
    public void onMessage() throws ExecutionException, InterruptedException, TimeoutException {
        /*
         * Given
         */
        String testMessage = UUID.randomUUID().toString();
        CompletableFuture<String> completableFuture = new CompletableFuture<>();

        Mockito.doAnswer(invocation -> {
            Message message = invocation.getArgument(0);
            completableFuture.complete(new String(message.getData()));
            return null;
        }).when(messageHandlerInterfaces.get(0)).handleMessage(Mockito.any(Message.class));

        /*
         * When
         */
        nc.publish(SUBJECT, testMessage.getBytes());

        /*
         * Then
         */
        String message = completableFuture.get(2, TimeUnit.SECONDS);
        Assertions.assertEquals(testMessage, message);
    }
}

推荐方案

个人最推荐方法一,它完全利用了Spring自动聚合同类型Bean到List的特性,不需要手动处理注入或反射,代码最简洁也最符合Spring测试的设计理念。

内容的提问来源于stack exchange,提问作者Moritz Schmitz v. Hülst

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 03:58:10