Spring、Spring Boot和TestNG測試指南 – @TestConfiguration

chanjarster發表於2017-09-04

Github地址

@TestConfiguration是Spring Boot Test提供的一種工具,用它我們可以在一般的@Configuration之外補充測試專門用的Bean或者自定義的配置。

@TestConfiguration實際上是一種@TestComponent@TestComponent是另一種@Component,在語義上用來指定某個Bean是專門用於測試的。

需要特別注意,你應該使用一切辦法避免在生產程式碼中自動掃描到@TestComponent
如果你使用@SpringBootApplication啟動測試或者生產程式碼,@TestComponent會自動被排除掉,如果不是則需要像@SpringBootApplication一樣新增TypeExcludeFilter

//...
@ComponentScan(excludeFilters = {
  @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  // ...})
public @interface SpringBootApplication

例子1:作為內部類

@TestConfiguration和@Configuration不同,它不會阻止@SpringBootTest去查詢機制(在Chapter 1: 基本用法 – 使用Spring Boot Testing工具 – 例子4提到過),正如@TestConfiguration的javadoc所說,它只是對既有配置的一個補充。

所以我們在測試程式碼上新增@SpringBootConfiguration,用@SpringBootTest(classes=...)或者在同package裡新增@SpringBootConfiguration類都是可以的。

而且@TestConfiguration作為內部類的時候它是會被@SpringBootTest掃描掉的,這點和@Configuration一樣。

測試程式碼TestConfigurationTest

@SpringBootTest
@SpringBootConfiguration
public class TestConfigurationTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private Foo foo;

  @Test
  public void testPlusCount() throws Exception {
    assertEquals(foo.getName(), "from test config");
  }

  @TestConfiguration
  public class TestConfig {

    @Bean
    public Foo foo() {
      return new Foo("from test config");
    }

  }
}

例子2:對@Configuration的補充和覆蓋

@TestConfiguration能夠:

  1. 補充額外的Bean

  2. 覆蓋已存在的Bean

要特別注意第二點,@TestConfiguration能夠直接覆蓋已存在的Bean,這一點正常的@Configuration是做不到的。

我們先提供了一個正常的@Configuration(Config):

@Configuration
public class Config {

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

又提供了一個@TestConfiguration,在裡面覆蓋了foo Bean,並且提供了foo2 Bean(TestConfig):

@TestConfiguration
public class TestConfig {

  // 這裡不需要@Primary之類的機制,直接就能夠覆蓋
  @Bean
  public Foo foo() {
    return new Foo("from test config");
  }

  @Bean
  public Foo foo2() {
    return new Foo("from test config2");
  }
}

測試程式碼TestConfigurationTest

@SpringBootTest(classes = { Config.class, TestConfig.class })
public class TestConfigurationTest extends AbstractTestNGSpringContextTests {

  @Qualifier("foo")
  @Autowired
  private Foo foo;

  @Qualifier("foo2")
  @Autowired
  private Foo foo2;

  @Test
  public void testPlusCount() throws Exception {
    assertEquals(foo.getName(), "from test config");
    assertEquals(foo2.getName(), "from test config2");

  }

}

再檢視輸出的日誌,就會發現Auto Configuration已經關閉。

例子3:避免@TestConfiguration被掃描到

在上面的這個例子裡的TestConfig是會被@ComponentScan掃描到的,如果要避免被掃描到,在本文開頭已經提到過了。

先來看一下沒有做任何過濾的情形,我們先提供了一個@SpringBootConfiguration(IncludeConfig):

@SpringBootConfiguration
@ComponentScan
public interface IncludeConfig {
}

然後有個測試程式碼引用了它(TestConfigIncludedTest):

@SpringBootTest(classes = IncludeConfig.class)
public class TestConfigIncludedTest extends AbstractTestNGSpringContextTests {

  @Autowired(required = false)
  private TestConfig testConfig;

  @Test
  public void testPlusCount() throws Exception {
    assertNotNull(testConfig);

  }

}

從這段程式碼可以看到TestConfig被載入了。

現在我們使用TypeExcludeFilter來過濾@TestConfiguration(ExcludeConfig1):

@SpringBootConfiguration
@ComponentScan(excludeFilters = {
    @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
})
public interface ExcludeConfig1 {
}

再來看看結果(TestConfigExclude_1_Test):

@SpringBootTest(classes = ExcludeConfig1.class)
public class TestConfigExclude_1_Test extends AbstractTestNGSpringContextTests {

  @Autowired(required = false)
  private TestConfig testConfig;

  @Test
  public void test() throws Exception {
    assertNull(testConfig);

  }

}

還可以用@SpringBootApplication來排除TestConfigExcludeConfig2):

@SpringBootApplication
public interface ExcludeConfig2 {
}

看看結果(TestConfigExclude_2_Test):

@SpringBootTest(classes = ExcludeConfig2.class)
public class TestConfigExclude_2_Test extends AbstractTestNGSpringContextTests {

  @Autowired(required = false)
  private TestConfig testConfig;

  @Test
  public void testPlusCount() throws Exception {
    assertNull(testConfig);

  }

}

參考文件

相關文章