Spring 单元测试:使用@SpyBean 模拟 void 方法:为该 bean 获取 NullPointerException,除非我改用 @Mock
Spring Unit Testing: Using @SpyBean to mock a void method: getting NullPointerException for that bean unless I use @Mock instead
我正在尝试让@SpyBean 或@MockBean 在这个测试中工作,因为它们在我的所有其他测试中工作。这里唯一的区别是这个测试使用了一个活动配置文件,因为它模拟了一些 AWS 库
如果我使用的是 SpyBean,我就不必实例化 class 它,我已经看到无数与我下面类似的示例,它们似乎工作正常。
我正在测试的单元:
@Component
public class SqsAdapter {
@Autowired
QueueMessagingTemplate queueMessagingTemplate;
@Autowired
MyProcessor processor;
@SqsListener("${queue.name}")
public void onEvent(String message) {
if (!StringUtils.isEmpty(message)) {
processor.process(message);
} else {
log.error("Empty or null message received from queue");
}
}
}
和一个简单的单元测试,期望调用 process(...):
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@ActiveProfiles("local")
public class SqsAdapterTest {
@Autowired
AmazonSQSAsync amazonSQS;
@SpyBean
MyProcessor processor;
@InjectMocks
SqsAdapter adapter;
@Test
public void testOnEvent() {
doNothing().when(processor).process(any()); // <---- process() is void
String event = "royale with cheese";
adapter.onEvent(event);
verify(processor, times(1)).process(event);
}
}
当我在单元测试中调试时,我在 processor
上得到一个 java.lang.NullPointerException
,就好像它从未被初始化过一样:
我也试过将它切换到@MockBean,结果相同
此外,我还尝试剥离对@SpringBootTest 和@ActiveProfiles 的测试...并删除了 AmazonSQSAsync 依赖项,因为我没有模拟 Listener 本身,它仍然会抛出相同的 NPE
我在这里错过了什么?
我猜 Spring 引导测试不会在此测试配置中加载 MyProcessor
bean。
通常,如果加载了 bean,则 @SpyBean
注释可以指示 spring 包装原始 bean,以便您获得间谍。但是,您必须提供 spring 引导测试配置,使其能够找到原始的 MyProcessor bean。
如果你有配置class,输入breakpoint/log:
@Configuration
public class MyConfiguration {
@Bean
public MyProcessor myProcessor(...) {
return new MyProcessor(); // <-- put breakpoint here and see whether its called at all
}
}
或者,如果您有类似的东西:
@Component
public class MyProcessor {
.... <-- put a breakpoint in constructor (create a default one if you don't have any)
}
如果我的假设是正确的,现在您应该看到根本没有调用此 class,并且您了解 为什么 设置不起作用。
现在,用 @SpringBootTest
代替 @SpringBootTest(classes = Application.class)
这试图通过递归扫描配置和所有内容来模仿 spring 启动应用程序启动过程(这是一个广泛的主题,实际上您应该阅读 spring 启动测试的文档)。
相反,你所做的是对 spring 说的:"I just want to work with predefined configuration class Application
and that's it, don't scan anything, don't resolve configurations, don't resolve beans, I know what I'm doing"。
如果您遵循 spring 引导提供的包约定,在这一步之后,您应该会看到加载了原始 MyProcessor
bean 并创建了 spy
我想我已经回答了:似乎是因为通过将@InjectMocks 注释与@SpyBean 混合来混合测试框架...因为我试图不使用 Mockito 模拟,这是一个 Mockito 注释,我认为它搞砸了测试
工作单元测试如下所示:
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("local")
public class SqsAdapterTest {
@Autowired
AmazonSQSAsync amazonSQS;
@SpyBean
SeamEventProcessor seamEventProcessor;
@Autowired
SqsAdapter adapter;
@Test
public void testOnEvent() {
doNothing().when(seamEventProcessor).process(any());
String event = "royale with cheese";
adapter.onEvent(event);
verify(seamEventProcessor, times(1)).process(event);
}
}
我正在尝试让@SpyBean 或@MockBean 在这个测试中工作,因为它们在我的所有其他测试中工作。这里唯一的区别是这个测试使用了一个活动配置文件,因为它模拟了一些 AWS 库
如果我使用的是 SpyBean,我就不必实例化 class 它,我已经看到无数与我下面类似的示例,它们似乎工作正常。
我正在测试的单元:
@Component
public class SqsAdapter {
@Autowired
QueueMessagingTemplate queueMessagingTemplate;
@Autowired
MyProcessor processor;
@SqsListener("${queue.name}")
public void onEvent(String message) {
if (!StringUtils.isEmpty(message)) {
processor.process(message);
} else {
log.error("Empty or null message received from queue");
}
}
}
和一个简单的单元测试,期望调用 process(...):
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@ActiveProfiles("local")
public class SqsAdapterTest {
@Autowired
AmazonSQSAsync amazonSQS;
@SpyBean
MyProcessor processor;
@InjectMocks
SqsAdapter adapter;
@Test
public void testOnEvent() {
doNothing().when(processor).process(any()); // <---- process() is void
String event = "royale with cheese";
adapter.onEvent(event);
verify(processor, times(1)).process(event);
}
}
当我在单元测试中调试时,我在 processor
上得到一个 java.lang.NullPointerException
,就好像它从未被初始化过一样:
我也试过将它切换到@MockBean,结果相同
此外,我还尝试剥离对@SpringBootTest 和@ActiveProfiles 的测试...并删除了 AmazonSQSAsync 依赖项,因为我没有模拟 Listener 本身,它仍然会抛出相同的 NPE
我在这里错过了什么?
我猜 Spring 引导测试不会在此测试配置中加载 MyProcessor
bean。
通常,如果加载了 bean,则 @SpyBean
注释可以指示 spring 包装原始 bean,以便您获得间谍。但是,您必须提供 spring 引导测试配置,使其能够找到原始的 MyProcessor bean。
如果你有配置class,输入breakpoint/log:
@Configuration
public class MyConfiguration {
@Bean
public MyProcessor myProcessor(...) {
return new MyProcessor(); // <-- put breakpoint here and see whether its called at all
}
}
或者,如果您有类似的东西:
@Component
public class MyProcessor {
.... <-- put a breakpoint in constructor (create a default one if you don't have any)
}
如果我的假设是正确的,现在您应该看到根本没有调用此 class,并且您了解 为什么 设置不起作用。
现在,用 @SpringBootTest
@SpringBootTest(classes = Application.class)
这试图通过递归扫描配置和所有内容来模仿 spring 启动应用程序启动过程(这是一个广泛的主题,实际上您应该阅读 spring 启动测试的文档)。
相反,你所做的是对 spring 说的:"I just want to work with predefined configuration class Application
and that's it, don't scan anything, don't resolve configurations, don't resolve beans, I know what I'm doing"。
如果您遵循 spring 引导提供的包约定,在这一步之后,您应该会看到加载了原始 MyProcessor
bean 并创建了 spy
我想我已经回答了:似乎是因为通过将@InjectMocks 注释与@SpyBean 混合来混合测试框架...因为我试图不使用 Mockito 模拟,这是一个 Mockito 注释,我认为它搞砸了测试
工作单元测试如下所示:
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("local")
public class SqsAdapterTest {
@Autowired
AmazonSQSAsync amazonSQS;
@SpyBean
SeamEventProcessor seamEventProcessor;
@Autowired
SqsAdapter adapter;
@Test
public void testOnEvent() {
doNothing().when(seamEventProcessor).process(any());
String event = "royale with cheese";
adapter.onEvent(event);
verify(seamEventProcessor, times(1)).process(event);
}
}