測試工具

一些在測試應用時通常很有用的測試工具類打包在 spring-boot 中。

ConfigDataApplicationContextInitializer

ConfigDataApplicationContextInitializer 是一個 ApplicationContextInitializer,你可以將其應用於測試中來載入 Spring Boot application.properties 檔案。當你不需要 @SpringBootTest 提供的全部功能時,可以使用它,如下例所示:

  • Java

  • Kotlin

import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;

@ContextConfiguration(classes = Config.class, initializers = ConfigDataApplicationContextInitializer.class)
class MyConfigFileTests {

	// ...

}
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer
import org.springframework.test.context.ContextConfiguration

@ContextConfiguration(classes = [Config::class], initializers = [ConfigDataApplicationContextInitializer::class])
class MyConfigFileTests {

	// ...

}
單獨使用 ConfigDataApplicationContextInitializer 不提供 @Value("${…​}") 注入的支援。它的唯一作用是確保將 application.properties 檔案載入到 Spring 的 Environment 中。要獲得 @Value 支援,你需要額外配置一個 PropertySourcesPlaceholderConfigurer 或使用 @SpringBootTest,後者會自動為你配置一個。

TestPropertyValues

TestPropertyValues 允許你快速向 ConfigurableEnvironmentConfigurableApplicationContext 新增屬性。你可以透過 key=value 字串來呼叫它,如下所示:

  • Java

  • Kotlin

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.mock.env.MockEnvironment;

import static org.assertj.core.api.Assertions.assertThat;

class MyEnvironmentTests {

	@Test
	void testPropertySources() {
		MockEnvironment environment = new MockEnvironment();
		TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment);
		assertThat(environment.getProperty("name")).isEqualTo("Boot");
	}

}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.mock.env.MockEnvironment

class MyEnvironmentTests {

	@Test
	fun testPropertySources() {
		val environment = MockEnvironment()
		TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment)
		assertThat(environment.getProperty("name")).isEqualTo("Boot")
	}

}

OutputCaptureExtension

OutputCaptureExtension 是一個 JUnit Extension,你可以使用它來捕獲 System.outSystem.err 的輸出。要使用它,新增 @ExtendWith(OutputCaptureExtension.class) 並將 CapturedOutput 作為引數注入到測試類建構函式或測試方法中,如下所示:

  • Java

  • Kotlin

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {

	@Test
	void testName(CapturedOutput output) {
		System.out.println("Hello World!");
		assertThat(output).contains("World");
	}

}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension

@ExtendWith(OutputCaptureExtension::class)
class MyOutputCaptureTests {

	@Test
	fun testName(output: CapturedOutput?) {
		println("Hello World!")
		assertThat(output).contains("World")
	}

}

TestRestTemplate

TestRestTemplate 是 Spring RestTemplate 的一個便捷替代方案,在整合測試中很有用。你可以獲取一個普通的模板,或者一個傳送基本 HTTP 認證(帶使用者名稱和密碼)的模板。在任何情況下,該模板都是容錯的。這意味著它以一種對測試友好的方式執行,不會對 4xx 和 5xx 錯誤丟擲異常。相反,可以透過返回的 ResponseEntity 及其狀態碼來檢測此類錯誤。

Spring Framework 5.0 提供了一個新的 WebTestClient,它適用於 WebFlux 整合測試以及 WebFlux 和 MVC 的端到端測試。與 TestRestTemplate 不同,它提供了一個用於斷言的流式 API。

推薦(但非強制)使用 Apache HTTP Client(版本 5.1 或更高)。如果你的 classpath 中有它,TestRestTemplate 會相應地配置客戶端。如果你確實使用 Apache 的 HTTP 客戶端,會啟用一些額外的測試友好特性:

  • 不跟隨重定向(以便你可以斷言響應位置)。

  • 忽略 Cookie(因此模板是無狀態的)。

TestRestTemplate 可以直接在你的整合測試中例項化,如下例所示:

  • Java

  • Kotlin

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;

import static org.assertj.core.api.Assertions.assertThat;

class MyTests {

	private final TestRestTemplate template = new TestRestTemplate();

	@Test
	void testRequest() {
		ResponseEntity<String> headers = this.template.getForEntity("https://myhost.example.com/example", String.class);
		assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com");
	}

}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.boot.test.web.client.TestRestTemplate

class MyTests {

	private val template = TestRestTemplate()

	@Test
	fun testRequest() {
		val headers = template.getForEntity("https://myhost.example.com/example", String::class.java)
		assertThat(headers.headers.location).hasHost("other.example.com")
	}

}

或者,如果你使用帶有 WebEnvironment.RANDOM_PORTWebEnvironment.DEFINED_PORT@SpringBootTest 註解,則可以注入一個完全配置好的 TestRestTemplate 並開始使用它。如有必要,可以透過 RestTemplateBuilder Bean 應用額外的定製。任何未指定主機和埠的 URL 會自動連線到嵌入式伺服器,如下例所示:

  • Java

  • Kotlin

import java.time.Duration;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests {

	@Autowired
	private TestRestTemplate template;

	@Test
	void testRequest() {
		HttpHeaders headers = this.template.getForEntity("/example", String.class).getHeaders();
		assertThat(headers.getLocation()).hasHost("other.example.com");
	}

	@TestConfiguration(proxyBeanMethods = false)
	static class RestTemplateBuilderConfiguration {

		@Bean
		RestTemplateBuilder restTemplateBuilder() {
			return new RestTemplateBuilder().connectTimeout(Duration.ofSeconds(1)).readTimeout(Duration.ofSeconds(1));
		}

	}

}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import java.time.Duration

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests(@Autowired val template: TestRestTemplate) {

	@Test
	fun testRequest() {
		val headers = template.getForEntity("/example", String::class.java).headers
		assertThat(headers.location).hasHost("other.example.com")
	}

	@TestConfiguration(proxyBeanMethods = false)
	internal class RestTemplateBuilderConfiguration {

		@Bean
		fun restTemplateBuilder(): RestTemplateBuilder {
			return RestTemplateBuilder().connectTimeout(Duration.ofSeconds(1))
				.readTimeout(Duration.ofSeconds(1))
		}

	}

}