如何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
相关产品推荐
相关产品推荐

