spring 如何在 SpringJunit4TestRunner 中将 @ComponentScan 与测试特定的 ContextConfigurations 一起使用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39300167/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 01:12:21  来源:igfitidea点击:

How to use @ComponentScan together with test-specific ContextConfigurations in SpringJunit4TestRunner?

springspring-bootspring-test

提问by Jonathan Fuerth

I am testing a Spring Boot application. I have several test classes, each of which needs a different set of mocked or otherwise customized beans.

我正在测试 Spring Boot 应用程序。我有几个测试类,每个类都需要一组不同的模拟或以其他方式定制的 bean。

Here is a sketch of the setup:

这是设置的草图:

src/main/java:

源代码/主/Java:

package com.example.myapp;

@SpringBootApplication
@ComponentScan(
        basePackageClasses = {
                MyApplication.class,
                ImportantConfigurationFromSomeLibrary.class,
                ImportantConfigurationFromAnotherLibrary.class})
@EnableFeignClients
@EnableHystrix
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

package com.example.myapp.feature1;

@Component
public class Component1 {
    @Autowired
    ServiceClient serviceClient;

    @Autowired
    SpringDataJpaRepository dbRepository;

    @Autowired
    ThingFromSomeLibrary importantThingIDontWantToExplicitlyConstructInTests;

    // methods I want to test...
}

src/test/java:

源代码/测试/Java:

package com.example.myapp;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@ActiveProfiles("test")
public class Component1TestWithFakeCommunication {

    @Autowired
    Component1 component1; // <-- the thing we're testing. wants the above mock implementations of beans wired into it.

    @Autowired
    ServiceClient mockedServiceClient;

    @Configuration
    static class ContextConfiguration {
        @Bean
        @Primary
        public ServiceClient mockedServiceClient() {
            return mock(ServiceClient.class);
        }
    }

    @Before
    public void setup() {
        reset(mockedServiceClient);
    }

    @Test
    public void shouldBehaveACertainWay() {
        // customize mock, call component methods, assert results...
    }
}

package com.example.myapp;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@ActiveProfiles("test")
public class Component1TestWithRealCommunication {

    @Autowired
    Component1 component1; // <-- the thing we're testing. wants the real implementations in this test.

    @Autowired
    ServiceClient mockedServiceClient;

    @Before
    public void setup() {
        reset(mockedServiceClient);
    }

    @Test
    public void shouldBehaveACertainWay() {
        // call component methods, assert results...
    }
}

The problem with the above setup is that the component scan configured in MyApplication picks up Component1TestWithFakeCommunication.ContextConfiguration, so I get a mock ServiceClient even in Component1TestWithRealCommunication where I want the real ServiceClient implementation.

上述设置的问题在于,在 MyApplication 中配置的组件扫描选择了 Component1TestWithFakeCommunication.ContextConfiguration,所以即使在我想要真正的 ServiceClient 实现的 Component1TestWithRealCommunication 中,我也得到了一个模拟 ServiceClient。

Although I could use @Autowired constructors and build up the components myself in both tests, there is a sufficient amount of stuff with complicated setup that I would rather have Spring TestContext set up for me (for example, Spring Data JPA repositories, components from libraries outside the app that pull beans from the Spring context, etc.). Nesting a Spring configuration inside the test that can locally override certain bean definitions within the Spring context feels like it should be a clean way to do this; the only downfall is that these nested configurations end up affecting all Spring TestContext tests that base their configuration on MyApplication (which component scans the app package).

尽管我可以在两个测试中使用 @Autowired 构造函数并自己构建组件,但有足够多的东西具有复杂的设置,我宁愿为我设置 Spring TestContext(例如,Spring Data JPA 存储库、库中的组件)在从 Spring 上下文等中提取 bean 的应用程序之外)。在测试中嵌套一个 Spring 配置,它可以本地覆盖 Spring 上下文中的某些 bean 定义,感觉应该是一种干净的方式来做到这一点;唯一的缺点是这些嵌套配置最终会影响所有基于 MyApplication(该组件扫描应用程序包)的配置的 Spring TestContext 测试。

How do I modify my setup so I still get a "mostly real" Spring context for my tests with just a few locally overridden beans in each test class?

如何修改我的设置,以便在每个测试类中只用几个本地覆盖的 bean 为我的测试获得“大部分真实”的 Spring 上下文?

采纳答案by Sam Brannen

The following should help you to achieve your goal by introducing a new fake-communicationprofilethat is applicable only to the current test class.

以下内容应通过引入仅适用于当前测试类的新fake-communication配置文件来帮助您实现目标。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@ActiveProfiles({"test", "fake-communication"})
public class Component1TestWithFakeCommunication {

    // @Autowired ...

    @Profile("fake-communication")
    @Configuration
    static class ContextConfiguration {
        @Bean
        @Primary
        public ServiceClient mockedServiceClient() {
            return mock(ServiceClient.class);
        }
    }
}

回答by Jorge Viana

If you have a @SpringBootTestyou can just annotate the service you want to mock with @MockBean. As simple as that.

如果你有一个,@SpringBootTest你可以只注释你想要模拟的服务@MockBean。就如此容易。

回答by Marwin

You may use additional explicit profiles to avoid such test configurations to be picked up (as suggested in another answer). I also did it and even created some library support for that.

您可以使用其他显式配置文件来避免选择此类测试配置(如另一个答案中所建议)。我也这样做了,甚至为此创建了一些库支持。

However, Spring-Boot is clever and it has a built-in "type filter" to resolve this issue automatically. For this to work, you need to remove your @ComponentScanannotation, which would find your test configurations, and let the @SpringBootApplicationdo the work. In your example, just remove this:

然而,Spring-Boot 很聪明,它有一个内置的“类型过滤器”来自动解决这个问题。为此,您需要删除您的@ComponentScan注释,它会找到您的测试配置,并让其@SpringBootApplication完成工作。在您的示例中,只需删除此内容:

@SpringBootApplication
@ComponentScan(
    basePackageClasses = {
            MyApplication.class,
            ImportantConfigurationFromSomeLibrary.class,
            ImportantConfigurationFromAnotherLibrary.class})

and replace it with:

并将其替换为:

@SpringBootApplication(scanBasePackageClasses= {
            MyApplication.class,
            ImportantConfigurationFromSomeLibrary.class,
            ImportantConfigurationFromAnotherLibrary.class})

You may also need to annotate your test as @SpringBootTest. This should avoid auto-scanning any inner-class configurations (and components) except for those residing in the currenttest.

您可能还需要将您的测试注释为@SpringBootTest. 这应该避免自动扫描任何内部类配置(和组件),除了驻留在当前测试中的那些。

回答by Josh Ghiloni

I would do a couple of things:

我会做几件事:

  1. Move your test classes into a different package to avoid @ComponentScaning them.
  2. In Component1TestWithFakeCommunication, change @SpringApplicationConfiguration(classes = MyApplication.class)to @SpringApplicationConfiguration(classes = {MyApplication.class, Component1TestWithFakeCommunication.ContextConfiguration.class})
  1. 将您的测试类移动到不同的包中以避免@ComponentScan对它们进行ing。
  2. Component1TestWithFakeCommunication@SpringApplicationConfiguration(classes = MyApplication.class)改为@SpringApplicationConfiguration(classes = {MyApplication.class, Component1TestWithFakeCommunication.ContextConfiguration.class})

That should give Spring sufficient information to mock up the test beans, but it should prevent the runtime ApplicationContextfrom noticing your test beans as well.

这应该为 Spring 提供足够的信息来模拟测试 bean,但它也应该防止运行时ApplicationContext注意到您的测试 bean。