Spring、Spring Boot和TestNG測試指南 - 測試@Configuration 原 薦

碼雲地址

在Spring引入Java Config機制之後,我們會越來越多的使用@Configuration來註冊Bean,並且Spring Boot更廣泛地使用了這一機制,其提供的大量Auto Configuration大大簡化了配置工作。那麼問題來了,如何確保@Configuration和Auto Configuration按照預期運行呢,是否正確地註冊了Bean呢?本章舉例測試@Configuration和Auto Configuration的方法(因爲Auto Configuration也是@Configuration,所以測試方法是一樣的)。

例子1:測試@Configuration

我們先寫一個簡單的@Configuration:

@Configuration
public class FooConfiguration {

  @Bean
  public Foo foo() {
    return new Foo();
  }

}

然後看FooConfiguration是否能夠正確地註冊Bean:

public class FooConfigurationTest {

  private AnnotationConfigApplicationContext context;

  @BeforeMethod
  public void init() {
    context = new AnnotationConfigApplicationContext();
  }

  @AfterMethod(alwaysRun = true)
  public void reset() {
    context.close();
  }

  @Test
  public void testFooCreation() {
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

}

注意上面代碼中關於Context的代碼:

  1. 首先,我們構造一個Context
  2. 然後,註冊FooConfiguration
  3. 然後,refresh Context
  4. 最後,在測試方法結尾close Context

如果你看Spring Boot中關於@Configuration測試的源代碼會發現和上面的代碼有點不一樣:

public class DataSourceAutoConfigurationTests {

	private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

	@Before
	public void init() {
		EmbeddedDatabaseConnection.override = null;
		EnvironmentTestUtils.addEnvironment(this.context,
				"spring.datasource.initialize:false",
				"spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt());
	}

	@After
	public void restore() {
		EmbeddedDatabaseConnection.override = null;
		this.context.close();
	}

這是因爲Spring和Spring Boot都是用JUnit做測試的,而JUnit的特性是每次執行測試方法前,都會new一個測試類實例,而TestNG是在共享同一個測試類實例的。

例子2:測試@Conditional

Spring Framework提供了一種可以條件控制@Configuration的機制,即只在滿足某條件的情況下才會導入@Configuration,這就是@Conditional

下面我們來對@Conditional做一些測試,首先我們自定義一個Condition FooConfiguration

@Configuration
public class FooConfiguration {

  @Bean
  @Conditional(FooCondition.class)
  public Foo foo() {
    return new Foo();
  }

  public static class FooCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      if (context.getEnvironment() != null) {
        Boolean property = context.getEnvironment().getProperty("foo.create", Boolean.class);
        return Boolean.TRUE.equals(property);
      }
      return false;
    }

  }
}

該Condition判斷Environment中是否有foo.create=true

如果我們要測試這個Condition,那麼就必須往Environment裏添加相關property纔可以,在這裏我們測試了三種情況:

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

FooConfigurationTest

public class FooConfigurationTest {

  private AnnotationConfigApplicationContext context;

  @BeforeMethod
  public void init() {
    context = new AnnotationConfigApplicationContext();
  }

  @AfterMethod(alwaysRun = true)
  public void reset() {
    context.close();
  }

  @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  public void testFooCreatePropertyNull() {
    context.register(FooConfiguration.class);
    context.refresh();
    context.getBean(Foo.class);
  }

  @Test
  public void testFooCreatePropertyTrue() {
    context.getEnvironment().getPropertySources().addLast(
        new MapPropertySource("test", Collections.singletonMap("foo.create", "true"))
    );
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

  @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  public void testFooCreatePropertyFalse() {
    context.getEnvironment().getPropertySources().addLast(
        new MapPropertySource("test", Collections.singletonMap("foo.create", "false"))
    );
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

}

注意我們用以下方法來給Environment添加property:

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

所以針對@Conditional和其對應的Condition的測試的根本就是給它不一樣的條件,判斷其行爲是否正確,在這個例子裏我們的Condition比較簡單,只是判斷是否存在某個property,如果複雜Condition的話,測試思路也是一樣的。

例子3:測試@ConditionalOnProperty

Spring framework只提供了@Conditional,Spring boot對這個機制做了擴展,提供了更爲豐富的@ConditionalOn*,這裏我們以@ConditionalOnProperty舉例說明。

先看FooConfiguration

@Configuration
public class FooConfiguration {

  @Bean
  @ConditionalOnProperty(prefix = "foo", name = "create", havingValue = "true")
  public Foo foo() {
    return new Foo();
  }

}

FooConfigurationTest

public class FooConfigurationTest {

  private AnnotationConfigApplicationContext context;

  @BeforeMethod
  public void init() {
    context = new AnnotationConfigApplicationContext();
  }

  @AfterMethod(alwaysRun = true)
  public void reset() {
    context.close();
  }

  @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  public void testFooCreatePropertyNull() {
    context.register(FooConfiguration.class);
    context.refresh();
    context.getBean(Foo.class);
  }

  @Test
  public void testFooCreatePropertyTrue() {
    EnvironmentTestUtils.addEnvironment(context, "foo.create=true");
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

  @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  public void testFooCreatePropertyFalse() {
    EnvironmentTestUtils.addEnvironment(context, "foo.create=false");
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

}

這段測試代碼和例子2的邏輯差不多,只不過例子2裏使用了我們自己寫的Condition,這裏使用了Spring Boot提供的@ConditionalOnProperty。

並且利用了Spring Boot提供的[EnvironmentTestUtils][javadoc-spring-boot-EnvironmentTestUtils]簡化了給Environment添加property的工作:

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

例子4:測試Configuration Properties

Spring Boot還提供了類型安全的Configuration Properties,下面舉例如何對其進行測試。

BarConfiguration

@Configuration
@EnableConfigurationProperties(BarConfiguration.BarProperties.class)
public class BarConfiguration {

  @Autowired
  private BarProperties barProperties;

  @Bean
  public Bar bar() {
    return new Bar(barProperties.getName());
  }

  @ConfigurationProperties("bar")
  public static class BarProperties {

    private String name;

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }
  }

}

BarConfigurationTest

public class BarConfigurationTest {

  private AnnotationConfigApplicationContext context;

  @BeforeMethod
  public void init() {
    context = new AnnotationConfigApplicationContext();
  }

  @AfterMethod(alwaysRun = true)
  public void reset() {
    context.close();
  }

  @Test
  public void testBarCreation() {
    EnvironmentTestUtils.addEnvironment(context, "bar.name=test");
    context.register(BarConfiguration.class, PropertyPlaceholderAutoConfiguration.class);
    context.refresh();
    assertEquals(context.getBean(Bar.class).getName(), "test");
  }

}

注意到因爲我們使用了Configuration Properties機制,需要註冊PropertyPlaceholderAutoConfiguration,否則在BarConfiguration裏無法注入BarProperties。

參考文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章