在应用程序启动之前配置@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的初始化是在@BeforeMockito的注解之前触发的,所以在@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 没有按预期工作

为啥弹簧测试失败,不起作用@MockBean

如何在 API 调用完成之前防止 Angular 4.0 中的应用程序组件呈现

Angularjs2 - 在应用程序启动之前预加载服务器配置[重复]

Android跨应用启动

应用程序无法启动因为并行配置不正确的解决办法