在Spring配置中模拟命名的bean,而不使用allow-bean-definition-overriding,怎么办?

huangapple go评论92阅读模式
英文:

Mocked named beans in Spring configuration without using allow-bean-definition-overriding?

问题

以下是翻译好的内容:

我有两个具有相同签名的 beans。它们被命名,以便为请求它们的类获取正确的实例。

  1. @Configuration
  2. public class MyConfiguration {
  3. @Bean("durationForX")
  4. public Duration durationForX() {
  5. return Duration.ofSeconds(1);
  6. }
  7. @Bean("durationForY")
  8. public Duration durationForY() {
  9. return Duration.ofSeconds(5);
  10. }
  11. }

并且在以下情况下使用:

  1. @Component
  2. public class MyService {
  3. public MyService(
  4. @Qualifier("durationForX") duration
  5. ) {
  6. ...
  7. }
  8. }

Y的情况类似。

现在,我想在集成测试中自动装配上述 beans 的模拟。我尝试了以下方法:

  1. @Configuration
  2. @Profile("my-test-profile")
  3. public class IntegrationTestConfiguration {
  4. @Primary
  5. @Bean("durationForX")
  6. public Duration durationForXMock() {
  7. return Duration.ofMillis(100);
  8. }
  9. @Primary
  10. @Bean("durationForY")
  11. public Duration durationForYMock() {
  12. return Duration.ofMillis(500);
  13. }
  14. @Primary
  15. @Bean
  16. public AnotherService anotherService() {
  17. // 这个可以正常工作,可能是因为它不是命名 bean
  18. ...
  19. }
  20. }

但是,在运行集成测试时,会出现以下错误消息:

  1. ***************************
  2. APPLICATION FAILED TO START
  3. ***************************
  4. Description:
  5. The bean 'durationForX', defined in class path resource [com/../../MyConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [com/.../.../IntegrationTestConfiguration.class] and overriding is disabled.
  6. Action:
  7. Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

我不是在集成测试中自动装配实例本身,而只是在应用程序中调用一个入口点。

  1. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {MyApp.class})
  2. @ActiveProfiles("it")
  3. class MyIntegrationTest {
  4. @Autowired
  5. GraphQLTestTemplate graphQL;
  6. ...
  7. }

我不太愿意将 bean 覆盖设置为 true,因为我想控制在哪里使用哪些 bean。我期望对命名的 beans 进行模拟应遵循与非命名 bean 相同的模式,为什么会出现这种情况?有关解决方法的任何想法吗?

英文:

I have two beans with the same signature. They are named in order to get the correct instance to the classes requesting them.

  1. @Configuration
  2. public class MyConfiguration {
  3. @Bean("durationForX")
  4. public Duration durationForX() {
  5. return Duration.ofSeconds(1);
  6. }
  7. @Bean("durationForY")
  8. public Duration durationForY() {
  9. return Duration.ofSeconds(5);
  10. }
  11. }

and used as

  1. @Component
  2. public class MyService {
  3. public MyService(
  4. @Qualifier("durationForX") duration
  5. ) {
  6. ...
  7. }
  8. }

and similar for Y.

Now, I want to have mocks of the above beans autowired in an integration test. I've tried the following

  1. @Configuration
  2. @Profile("my-test-profile")
  3. public class IntegrationTestConfiguration {
  4. @Primary
  5. @Bean("durationForX")
  6. public Duration durationForXMock() {
  7. return Duration.ofMillis(100);
  8. }
  9. @Primary
  10. @Bean("durationForY")
  11. public Duration durationForYMock() {
  12. return Duration.ofMillis(500);
  13. }
  14. @Primary
  15. @Bean
  16. public AnotherService anotherService() {
  17. // This one works as expected, probably because it is not a named bean
  18. ...
  19. }
  20. }

which, when running the integration tests, results in the error message

  1. ***************************
  2. APPLICATION FAILED TO START
  3. ***************************
  4. Description:
  5. The bean 'durationForX', defined in class path resource [com/../../MyConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [com/.../.../IntegrationTestConfiguration.class] and overriding is disabled.
  6. Action:
  7. Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

I'm not auto-wiring the instances themselves in the integration tests, only one entry point for the application in order to call it.

  1. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {MyApp.class})
  2. @ActiveProfiles("it")
  3. class MyIntegrationTest {
  4. @Autowired
  5. GraphQLTestTemplate graphQL;
  6. ...
  7. }

I'm not too keen on setting the bean override to true as I want to be in control of which beans are used where. I would expect mocking the named beans to follow the same pattern as the not named one, why is this? Any idea on workarounds?

答案1

得分: 1

我建议在测试时使用不同的配置文件,例如您可以在主应用程序的主 application.yml 文件中定义以下值:

application.yml

  1. duration1: 1
  2. duration2: 5

然后在 MyConfiguration 类中使用 @Value 注解来读取它们:

  1. @Configuration
  2. public class MyConfiguration {
  3. @Value("${duration1}")
  4. private Integer duration1;
  5. @Value("${duration2}")
  6. private Integer duration2;
  7. @Bean("durationForX")
  8. public Duration durationForX() {
  9. return Duration.ofSeconds(duration1);
  10. }
  11. @Bean("durationForY")
  12. public Duration durationForY() {
  13. return Duration.ofSeconds(duration2);
  14. }
  15. }

现在,为了进行测试,在 src/main/resourcessrc/test/resources 下创建 application-test.yml 文件,然后添加测试值的属性:

application-test.yml

  1. duration1: 100
  2. duration2: 500

无需额外的 IntegrationTestConfiguration 文件,您可以将测试属性维护在 test.yml 文件中。

注意: 确保您的测试类使用 @Profile("test")@SpringBootTest 进行注解,以便加载带有相应测试属性的测试应用程序上下文:

  1. @SpringBootTest
  2. @Profile("test")
  3. public class AppTest {
  4. }
英文:

I would recommend using the different profile for test, for example you can define the values in main application.yml for main application

application.yml

  1. duration1:1
  2. duration2:5

And then in read them in MyConfiguration class using @Value annotation

  1. @Configuration
  2. public class MyConfiguration {
  3. @Value("${duration1})
  4. private Integer duration1;
  5. @Value("${duration2})
  6. private Integer duration2;
  7. @Bean("durationForX")
  8. public Duration durationForX() {
  9. return Duration.ofSeconds(duration1);
  10. }
  11. @Bean("durationForY")
  12. public Duration durationForY() {
  13. return Duration.ofSeconds(duration2);
  14. }
  15. }

Now for test create application-test.yml under src/main/resources or src/test/resources, then add the properties with test values

application-test.yml

  1. duration1:100
  2. duration2:500

No need of any IntegrationTestConfiguration file's you can just maintain test properties in test.yml file

Note : Make sure you annotate test class with @Profile("test") and @SpringBootTest to load the test ap[plication context with corresponding test properties

  1. @SpringBootTest
  2. @Profile("test)
  3. public class AppTest {
  4. }

huangapple
  • 本文由 发表于 2020年10月19日 21:20:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/64428221.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定