在应用程序启动之前配置@MockBean 组件
Posted
技术标签:
【中文标题】在应用程序启动之前配置@MockBean 组件【英文标题】:Configure @MockBean component before application start 【发布时间】:2017-03-26 14:16:34 【问题描述】:我有一个 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
-> TYPE_C
被返回。
问题是应用程序没有启动。目前弹簧的工作顺序似乎是:
-
创建所有 Mocks(无需配置所有方法都返回 null)
启动应用程序
调用@Before-methods(将在其中配置模拟)
开始测试
我的问题是应用程序以未初始化的模拟开始。所以对getSystemType()
的调用返回null。
我的问题是:如何在应用程序启动之前配置模拟?
编辑:如果有人遇到同样的问题,一个解决方法是使用@MockBean(answer = CALLS_REAL_METHODS)
。这调用了真正的组件,在我的情况下,系统启动了。启动后,我可以更改模拟行为。
【问题讨论】:
您可以手动注入模拟和调用初始化代码,如本答案所述:***.com/a/31587946/3440376 【参考方案1】:在这种情况下,您需要以我们在引入 @MockBean
之前使用的方式配置模拟 - 通过手动指定将替换上下文中原始的 @Primary
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 的方法。
【讨论】:
设置spring.main.allow-bean-definition-overriding=true
后生效
这在 Spring Boot 2.0.9 中工作......就像在原始答案中一样
这可行,但是如何更改每个测试用例的存根?
将when
从@Bean
注解的方法中移出,并在@BeforeEach
方法中对该bean调用reset
。【参考方案2】:
您可以使用以下技巧:
@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
【讨论】:
【参考方案3】:我可以这样修复它
@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();
【讨论】:
我使用了从 Maciej Walkowiak 创建模拟的解决方案,这种注入在 Spring Boot 2.0.9 中对我有用 -> 在模拟实例上使用 Autowired 而不是 MockBean【参考方案4】:Spring的初始化是在@Before
Mockito的注解之前触发的,所以在@PostConstruct
注解的方法被执行时,mock并没有被初始化。
尝试使用SystemTypeDetector
组件上的@Lazy
注释来“延迟”您的系统检测。在需要的地方使用您的 SystemTypeDetector
,请记住,您不能在 @PostConstruct
或等效挂钩中触发此检测。
【讨论】:
【参考方案5】:我认为这是由于您自动装配依赖项的方式。看看this(特别是关于“修复#1:解决您的设计并使您的依赖项可见”的部分)。这样你也可以避免使用@PostConstruct
,而只使用构造函数。
【讨论】:
【参考方案6】:你正在使用什么,对单元测试有好处:
org.mockito.Mockito#when()
尝试使用以下方法在上下文启动时模拟 spring bean:
org.mockito.BDDMockito#given()
如果你使用@SpyBean,那么你应该使用另一种语法:
willReturn(Arrays.asList(val1, val2))
.given(service).getEntities(any());
【讨论】:
这与问题无关。问题是关于 Spring-boot 测试的 MockBean,而不是常规 JUnit 测试中的简单模拟。以上是关于在应用程序启动之前配置@MockBean 组件的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot @WebMvcTest 和 @MockBean 没有按预期工作
如何在 API 调用完成之前防止 Angular 4.0 中的应用程序组件呈现