在应用程序启动之前配置@MockBean组件

时间:2016-11-12 13:36:48

标签: java spring spring-boot mocking integration-testing

我有一个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}}已退回。

问题是应用程序无法启动。目前弹簧工作顺序似乎是:

  1. 创建所有模拟(没有配置,所有方法都返回null)
  2. 启动申请
  3. 调用@ Before-methods(将配置模拟的地方)
  4. 开始测试
  5. 我的问题是应用程序以未初始化的模拟开始。因此,对TYPE_C的调用将返回null。

    我的问题是:如何在应用程序启动之前配置模拟

    修改:如果有人遇到同样的问题,则一个解决方法将使用getSystemType()。这称为真实组件,在我的情况下,系统启动。启动后,我可以改变模拟行为。

7 个答案:

答案 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,而只需使用构造函数即可。