Chapter 7: 测试@Configuration

在Spring引入Java Config机制之后,我们会越来越多的使用@Configuration来注册Bean,并且Spring Boot更广泛地使用了这一机制,其提供的大量Auto Configuration大大简化了配置工作。那么问题来了,如何确保@Configuration和Auto Configuration按照预期运行呢,是否正确地注册了Bean呢?本章举例测试@Configuration和Auto Configuration的方法(因为Auto Configuration也是@Configuration,所以测试方法是一样的)。

例子1:测试@Configuration

我们先写一个简单的@Configuration

  1. @Configuration
  2. public class FooConfiguration {
  3. @Bean
  4. public Foo foo() {
  5. return new Foo();
  6. }
  7. }

然后看FooConfiguration是否能够正确地注册Bean:

  1. public class FooConfigurationTest {
  2. private AnnotationConfigApplicationContext context;
  3. @BeforeMethod
  4. public void init() {
  5. context = new AnnotationConfigApplicationContext();
  6. }
  7. @AfterMethod(alwaysRun = true)
  8. public void reset() {
  9. context.close();
  10. }
  11. @Test
  12. public void testFooCreation() {
  13. context.register(FooConfiguration.class);
  14. context.refresh();
  15. assertNotNull(context.getBean(Foo.class));
  16. }
  17. }

注意上面代码中关于Context的代码:

  1. 首先,我们构造一个Context
  2. 然后,注册FooConfiguration
  3. 然后,refresh Context
  4. 最后,在测试方法结尾close Context

如果你看Spring Boot中关于@Configuration测试的源代码会发现和上面的代码有点不一样:

  1. public class DataSourceAutoConfigurationTests {
  2. private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  3. @Before
  4. public void init() {
  5. EmbeddedDatabaseConnection.override = null;
  6. EnvironmentTestUtils.addEnvironment(this.context,
  7. "spring.datasource.initialize:false",
  8. "spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt());
  9. }
  10. @After
  11. public void restore() {
  12. EmbeddedDatabaseConnection.override = null;
  13. this.context.close();
  14. }

这是因为Spring和Spring Boot都是用JUnit做测试的,而JUnit的特性是每次执行测试方法前,都会new一个测试类实例,而TestNG是在共享同一个测试类实例的。

例子2:测试@Conditional

Spring Framework提供了一种可以条件控制@Configuration的机制,即只在满足某条件的情况下才会导入@Configuration,这就是@Conditional

下面我们来对@Conditional做一些测试,首先我们自定义一个Condition FooConfiguration

  1. @Configuration
  2. public class FooConfiguration {
  3. @Bean
  4. @Conditional(FooCondition.class)
  5. public Foo foo() {
  6. return new Foo();
  7. }
  8. public static class FooCondition implements Condition {
  9. @Override
  10. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  11. if (context.getEnvironment() != null) {
  12. Boolean property = context.getEnvironment().getProperty("foo.create", Boolean.class);
  13. return Boolean.TRUE.equals(property);
  14. }
  15. return false;
  16. }
  17. }
  18. }

该Condition判断Environment中是否有foo.create=true

如果我们要测试这个Condition,那么就必须往Environment里添加相关property才可以,在这里我们测试了三种情况:

  1. 没有配置foo.create=true
  2. 配置foo.create=true
  3. 配置foo.create=false

FooConfigurationTest

  1. public class FooConfigurationTest {
  2. private AnnotationConfigApplicationContext context;
  3. @BeforeMethod
  4. public void init() {
  5. context = new AnnotationConfigApplicationContext();
  6. }
  7. @AfterMethod(alwaysRun = true)
  8. public void reset() {
  9. context.close();
  10. }
  11. @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  12. public void testFooCreatePropertyNull() {
  13. context.register(FooConfiguration.class);
  14. context.refresh();
  15. context.getBean(Foo.class);
  16. }
  17. @Test
  18. public void testFooCreatePropertyTrue() {
  19. context.getEnvironment().getPropertySources().addLast(
  20. new MapPropertySource("test", Collections.singletonMap("foo.create", "true"))
  21. );
  22. context.register(FooConfiguration.class);
  23. context.refresh();
  24. assertNotNull(context.getBean(Foo.class));
  25. }
  26. @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  27. public void testFooCreatePropertyFalse() {
  28. context.getEnvironment().getPropertySources().addLast(
  29. new MapPropertySource("test", Collections.singletonMap("foo.create", "false"))
  30. );
  31. context.register(FooConfiguration.class);
  32. context.refresh();
  33. assertNotNull(context.getBean(Foo.class));
  34. }
  35. }

注意我们用以下方法来给Environment添加property:

  1. context.getEnvironment().getPropertySources().addLast(
  2. new MapPropertySource("test", Collections.singletonMap("foo.create", "true"))
  3. );

所以针对@Conditional和其对应的Condition的测试的根本就是给它不一样的条件,判断其行为是否正确,在这个例子里我们的Condition比较简单,只是判断是否存在某个property,如果复杂Condition的话,测试思路也是一样的。

例子3:测试@ConditionalOnProperty

Spring framework只提供了@Conditional,Spring boot对这个机制做了扩展,提供了更为丰富的@ConditionalOn*,这里我们以@ConditionalOnProperty举例说明。

先看FooConfiguration

  1. @Configuration
  2. public class FooConfiguration {
  3. @Bean
  4. @ConditionalOnProperty(prefix = "foo", name = "create", havingValue = "true")
  5. public Foo foo() {
  6. return new Foo();
  7. }
  8. }

FooConfigurationTest

  1. public class FooConfigurationTest {
  2. private AnnotationConfigApplicationContext context;
  3. @BeforeMethod
  4. public void init() {
  5. context = new AnnotationConfigApplicationContext();
  6. }
  7. @AfterMethod(alwaysRun = true)
  8. public void reset() {
  9. context.close();
  10. }
  11. @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  12. public void testFooCreatePropertyNull() {
  13. context.register(FooConfiguration.class);
  14. context.refresh();
  15. context.getBean(Foo.class);
  16. }
  17. @Test
  18. public void testFooCreatePropertyTrue() {
  19. EnvironmentTestUtils.addEnvironment(context, "foo.create=true");
  20. context.register(FooConfiguration.class);
  21. context.refresh();
  22. assertNotNull(context.getBean(Foo.class));
  23. }
  24. @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  25. public void testFooCreatePropertyFalse() {
  26. EnvironmentTestUtils.addEnvironment(context, "foo.create=false");
  27. context.register(FooConfiguration.class);
  28. context.refresh();
  29. assertNotNull(context.getBean(Foo.class));
  30. }
  31. }

这段测试代码和例子2的逻辑差不多,只不过例子2里使用了我们自己写的Condition,这里使用了Spring Boot提供的@ConditionalOnProperty

并且利用了Spring Boot提供的EnvironmentTestUtils简化了给Environment添加property的工作:

  1. EnvironmentTestUtils.addEnvironment(context, "foo.create=false");

例子4:测试Configuration Properties

Spring Boot还提供了类型安全的Configuration Properties,下面举例如何对其进行测试。

BarConfiguration

  1. @Configuration
  2. @EnableConfigurationProperties(BarConfiguration.BarProperties.class)
  3. public class BarConfiguration {
  4. @Autowired
  5. private BarProperties barProperties;
  6. @Bean
  7. public Bar bar() {
  8. return new Bar(barProperties.getName());
  9. }
  10. @ConfigurationProperties("bar")
  11. public static class BarProperties {
  12. private String name;
  13. public String getName() {
  14. return name;
  15. }
  16. public void setName(String name) {
  17. this.name = name;
  18. }
  19. }
  20. }

BarConfigurationTest

  1. public class BarConfigurationTest {
  2. private AnnotationConfigApplicationContext context;
  3. @BeforeMethod
  4. public void init() {
  5. context = new AnnotationConfigApplicationContext();
  6. }
  7. @AfterMethod(alwaysRun = true)
  8. public void reset() {
  9. context.close();
  10. }
  11. @Test
  12. public void testBarCreation() {
  13. EnvironmentTestUtils.addEnvironment(context, "bar.name=test");
  14. context.register(BarConfiguration.class, PropertyPlaceholderAutoConfiguration.class);
  15. context.refresh();
  16. assertEquals(context.getBean(Bar.class).getName(), "test");
  17. }
  18. }

注意到因为我们使用了Configuration Properties机制,需要注册PropertyPlaceholderAutoConfiguration,否则在BarConfiguration里无法注入BarProperties。

参考文档