如何在 SpringJunit4TestRunner 中将 @ComponentScan 与特定于测试的 ContextConfigurations 一起使用?
How to use @ComponentScan together with test-specific ContextConfigurations in SpringJunit4TestRunner?
我正在测试 Spring 启动应用程序。我有几个测试 classes,每个都需要一组不同的模拟或自定义 beans。
这是设置草图:
src/main/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:
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...
}
}
上述设置的问题是在 MyApplication 中配置的组件扫描选择 Component1TestWithFakeCommunication.ContextConfiguration,所以即使在我想要真正的 ServiceClient 实现的 Component1TestWithRealCommunication 中我也得到了一个模拟 ServiceClient。
虽然我可以使用@Autowired 构造函数并在两个测试中自己构建组件,但有足够多的东西具有复杂的设置,我宁愿为我设置 Spring TestContext(例如,Spring Data JPA 存储库,来自应用程序外部库的组件,这些组件从 Spring 上下文中提取 bean,等等)。在测试中嵌套一个 Spring 配置,可以在 Spring 上下文中本地覆盖某些 bean 定义,感觉这应该是一种干净的方法;唯一的缺点是这些嵌套配置最终会影响所有 Spring TestContext 测试,这些测试的配置基于 MyApplication(哪个组件扫描应用程序包)。
如何修改我的设置,以便我的测试仍然获得 "mostly real" Spring 上下文,每个测试中只有几个本地覆盖的 bean class?
我会做几件事:
- 将您的测试 类 移到另一个包中以避免
@ComponentScan
ing 它们。
- 在
Component1TestWithFakeCommunication
中,将@SpringApplicationConfiguration(classes = MyApplication.class)
更改为@SpringApplicationConfiguration(classes = {MyApplication.class, Component1TestWithFakeCommunication.ContextConfiguration.class})
这应该给 Spring 足够的信息来模拟测试 bean,但它应该防止运行时 ApplicationContext
也注意到您的测试 bean。
以下内容将通过引入仅适用于当前测试 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);
}
}
}
您可以使用额外的显式配置文件来避免此类测试配置被拾取(如另一个答案中所建议的)。我也这样做了,甚至为此创建了一些库支持。
但是,Spring-Boot 很聪明,它有一个内置的 "type filter" 来自动解决这个问题。为此,您需要删除 @ComponentScan
注释,它会找到您的测试配置,并让 @SpringBootApplication
完成工作。在您的示例中,只需删除此:
@SpringBootApplication
@ComponentScan(
basePackageClasses = {
MyApplication.class,
ImportantConfigurationFromSomeLibrary.class,
ImportantConfigurationFromAnotherLibrary.class})
并将其替换为:
@SpringBootApplication(scanBasePackageClasses= {
MyApplication.class,
ImportantConfigurationFromSomeLibrary.class,
ImportantConfigurationFromAnotherLibrary.class})
您可能还需要将测试注释为 @SpringBootTest
。这应该避免自动扫描任何内部 class 配置(和组件),除了 current 测试中的配置。
如果你有 @SpringBootTest
,你可以只用 @MockBean
注释你想模拟的服务。就这么简单。
我正在测试 Spring 启动应用程序。我有几个测试 classes,每个都需要一组不同的模拟或自定义 beans。
这是设置草图:
src/main/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:
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...
}
}
上述设置的问题是在 MyApplication 中配置的组件扫描选择 Component1TestWithFakeCommunication.ContextConfiguration,所以即使在我想要真正的 ServiceClient 实现的 Component1TestWithRealCommunication 中我也得到了一个模拟 ServiceClient。
虽然我可以使用@Autowired 构造函数并在两个测试中自己构建组件,但有足够多的东西具有复杂的设置,我宁愿为我设置 Spring TestContext(例如,Spring Data JPA 存储库,来自应用程序外部库的组件,这些组件从 Spring 上下文中提取 bean,等等)。在测试中嵌套一个 Spring 配置,可以在 Spring 上下文中本地覆盖某些 bean 定义,感觉这应该是一种干净的方法;唯一的缺点是这些嵌套配置最终会影响所有 Spring TestContext 测试,这些测试的配置基于 MyApplication(哪个组件扫描应用程序包)。
如何修改我的设置,以便我的测试仍然获得 "mostly real" Spring 上下文,每个测试中只有几个本地覆盖的 bean class?
我会做几件事:
- 将您的测试 类 移到另一个包中以避免
@ComponentScan
ing 它们。 - 在
Component1TestWithFakeCommunication
中,将@SpringApplicationConfiguration(classes = MyApplication.class)
更改为@SpringApplicationConfiguration(classes = {MyApplication.class, Component1TestWithFakeCommunication.ContextConfiguration.class})
这应该给 Spring 足够的信息来模拟测试 bean,但它应该防止运行时 ApplicationContext
也注意到您的测试 bean。
以下内容将通过引入仅适用于当前测试 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);
}
}
}
您可以使用额外的显式配置文件来避免此类测试配置被拾取(如另一个答案中所建议的)。我也这样做了,甚至为此创建了一些库支持。
但是,Spring-Boot 很聪明,它有一个内置的 "type filter" 来自动解决这个问题。为此,您需要删除 @ComponentScan
注释,它会找到您的测试配置,并让 @SpringBootApplication
完成工作。在您的示例中,只需删除此:
@SpringBootApplication
@ComponentScan(
basePackageClasses = {
MyApplication.class,
ImportantConfigurationFromSomeLibrary.class,
ImportantConfigurationFromAnotherLibrary.class})
并将其替换为:
@SpringBootApplication(scanBasePackageClasses= {
MyApplication.class,
ImportantConfigurationFromSomeLibrary.class,
ImportantConfigurationFromAnotherLibrary.class})
您可能还需要将测试注释为 @SpringBootTest
。这应该避免自动扫描任何内部 class 配置(和组件),除了 current 测试中的配置。
如果你有 @SpringBootTest
,你可以只用 @MockBean
注释你想模拟的服务。就这么简单。