我有一个Spring Boot 1.4.2应用程序。在启动期间使用的一些代码如下所示:
@Component class SystemTypeDetector{
public enum SystemType{ TYPE_A, TYPE_B, TYPE_C }
public SystemType getSystemType(){ return ... }
}
@Component public class SomeOtherComponent{
@Autowired private SystemTypeDetector systemTypeDetector;
@PostConstruct public void startup(){
switch(systemTypeDetector.getSystemType()){ // <-- NPE here in test
case TYPE_A: ...
case TYPE_B: ...
case TYPE_C: ...
}
}
}
有一个确定系统类型的组件。在从其他组件启动期间使用此组件。在生产中一切正常。
现在我想使用Spring 1.4的@MockBean添加一些集成测试。
测试如下:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
@MockBean private SystemTypeDetector systemTypeDetectorMock;
@Before public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
@Test public void testNrOne(){
// ...
}
}
基本上,模拟工作正常。我使用了systemTypeDetectorMock,如果我拨打getSystemType
- &gt; <{1}}已退回。
问题是应用程序无法启动。目前弹簧工作顺序似乎是:
我的问题是应用程序以未初始化的模拟开始。因此,对TYPE_C
的调用将返回null。
我的问题是:如何在应用程序启动之前配置模拟?
修改:如果有人遇到同样的问题,则一个解决方法将使用getSystemType()
。这称为真实组件,在我的情况下,系统启动。启动后,我可以改变模拟行为。
答案 0 :(得分:3)
在这种情况下,您需要以引入@MockBean
之前的方式配置模拟-通过手动指定@Primary
bean来替换上下文中的原始bean。
@SpringBootTest
class DemoApplicationTests {
@TestConfiguration
public static class TestConfig {
@Bean
@Primary
public SystemTypeDetector mockSystemTypeDetector() {
SystemTypeDetector std = mock(SystemTypeDetector.class);
when(std.getSystemType()).thenReturn(TYPE_C);
return std;
}
}
@Autowired
private SystemTypeDetector systemTypeDetector;
@Test
void contextLoads() {
assertThat(systemTypeDetector.getSystemType()).isEqualTo(TYPE_C);
}
}
由于@TestConfiguration
类是静态内部类,因此只有此测试才会自动选择该类。您将要放入@Before
中的完整模拟行为必须移至用于初始化bean的方法。
答案 1 :(得分:1)
您可以使用以下技巧:
@Configuration
public class Config {
@Bean
public BeanA beanA() {
return new BeanA();
}
@Bean
public BeanB beanB() {
return new BeanB(beanA());
}
}
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {TestConfig.class, Config.class})
public class ConfigTest {
@Configuration
static class TestConfig {
@MockBean
BeanA beanA;
@PostConstruct
void setUp() {
when(beanA.someMethod()).thenReturn(...);
}
}
}
至少适用于spring-boot-2.1.9.RELEASE
答案 2 :(得分:0)
Spring的初始化在@Before
Mockito的注释之前触发,因此在执行@PostConstruct
注释的方法时不会初始化模拟。
尝试使用@Lazy
组件上的SystemTypeDetector
注释“延迟”系统检测。请在需要的地方使用SystemTypeDetector
,请记住,您无法在@PostConstruct
或类似的钩子中触发此检测。
答案 3 :(得分:0)
您正在使用什么,对单元测试很有用:
org.mockito.Mockito#when()
尝试在上下文旋转时使用以下方法来模拟Spring bean:
org.mockito.BDDMockito#given()
如果您使用的是@SpyBean,则应使用另一种语法:
willReturn(Arrays.asList(val1, val2))
.given(service).getEntities(any());
答案 4 :(得分:0)
我能够像这样修复它
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
// this inner class must be static!
@TestConfiguration
public static class EarlyConfiguration {
@MockBean
private SystemTypeDetector systemTypeDetectorMock;
@PostConstruct
public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
}
// here we can inject the bean created by EarlyConfiguration
@Autowired
private SystemTypeDetector systemTypeDetectorMock;
@Autowired
private SomeOtherComponent someOtherComponent;
@Test
public void testNrOne(){
someOtherComponent.doStuff();
}
}
答案 5 :(得分:-1)
类SomeOtherComponent也需要@Component注释。
答案 6 :(得分:-1)
我认为这是由于您自动绑定依赖项的方式所致。看一下this(特别是有关“修复#1:解决您的设计并使您的依赖项可见”的部分)。这样,您还可以避免使用@PostConstruct
,而只需使用构造函数即可。