RestTestClient

RestTestClient is an HTTP client designed for testing server applications. It wraps Spring’s RestClient and uses it to perform requests, but exposes a testing facade for verifying responses. RestTestClient can be used to perform end-to-end HTTP tests. It can also be used to test Spring MVC applications without a running server via MockMvc.spring-doc.cn

Setup

To set up a RestTestClient you need to choose a server setup to bind to. This can be one of several MockMvc setup choices, or a connection to a live server.spring-doc.cn

Bind to Controller

This setup allows you to test specific controller(s) via mock request and response objects, without a running server.spring-doc.cn

RestTestClient client =
		RestTestClient.bindToController(new TestController()).build();
val client = RestTestClient.bindToController(TestController()).build()

Bind to ApplicationContext

This setup allows you to load Spring configuration with Spring MVC infrastructure and controller declarations and use it to handle requests via mock request and response objects, without a running server.spring-doc.cn

import org.junit.jupiter.api.BeforeEach;

import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.context.WebApplicationContext;


@SpringJUnitConfig(WebConfig.class) // Specify the configuration to load
public class RestClientContextTests {

	RestTestClient client;

	@BeforeEach
	void setUp(WebApplicationContext context) {  // Inject the configuration
		// Create the `RestTestClient`
		client = RestTestClient.bindToApplicationContext(context).build();
	}

}
import org.junit.jupiter.api.BeforeEach
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
import org.springframework.test.web.servlet.client.RestTestClient
import org.springframework.web.context.WebApplicationContext

@SpringJUnitConfig(WebConfig::class) // Specify the configuration to load
class RestClientContextTests {

	lateinit var client: RestTestClient

	@BeforeEach
	fun setUp(context: WebApplicationContext) {  // Inject the configuration
		// Create the `RestTestClient`
		client = RestTestClient.bindToApplicationContext(context).build()
	}
}

Bind to Router Function

This setup allows you to test functional endpoints via mock request and response objects, without a running server.spring-doc.cn

RouterFunction<?> route = ...
client = RestTestClient.bindToRouterFunction(route).build();
val route: RouterFunction<*> = ...
val client = RestTestClient.bindToRouterFunction(route).build()

Bind to Server

This setup connects to a running server to perform full, end-to-end HTTP tests:spring-doc.cn

client = RestTestClient.bindToServer().baseUrl("http://localhost:8080").build();
client = RestTestClient.bindToServer().baseUrl("http://localhost:8080").build()

Client Config

In addition to the server setup options described earlier, you can also configure client options, including base URL, default headers, client filters, and others. These options are readily available following the initial bindTo call, as follows:spring-doc.cn

client = RestTestClient.bindToController(new TestController())
		.baseUrl("/test")
		.build();
client = RestTestClient.bindToController(TestController())
		.baseUrl("/test")
		.build()

Writing Tests

RestClient and RestTestClient have the same API up to the point of the call to exchange(). After that, RestTestClient provides two alternative ways to verify the response:spring-doc.cn

  1. Built-in Assertions extend the request workflow with a chain of expectationsspring-doc.cn

  2. AssertJ Integration to verify the response via assertThat() statementsspring-doc.cn

Built-in Assertions

To use the built-in assertions, remain in the workflow after the call to exchange(), and use one of the expectation methods. For example:spring-doc.cn

client.get().uri("/persons/1")
		.accept(MediaType.APPLICATION_JSON)
		.exchange()
		.expectStatus().isOk()
		.expectHeader().contentType(MediaType.APPLICATION_JSON)
		.expectBody();
client.get().uri("/persons/1")
	.accept(MediaType.APPLICATION_JSON)
	.exchange()
	.expectStatus().isOk()
	.expectHeader().contentType(MediaType.APPLICATION_JSON)
	.expectBody()

If you would like for all expectations to be asserted even if one of them fails, you can use expectAll(..) instead of multiple chained expect*(..) calls. This feature is similar to the soft assertions support in AssertJ and the assertAll() support in JUnit Jupiter.spring-doc.cn

client.get().uri("/persons/1")
		.accept(MediaType.APPLICATION_JSON)
		.exchange()
		.expectAll(
				spec -> spec.expectStatus().isOk(),
				spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
		);
client.get().uri("/persons/1")
	.accept(MediaType.APPLICATION_JSON)
	.exchange()
	.expectAll(
		{ spec -> spec.expectStatus().isOk() },
		{ spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) }
	)

You can then choose to decode the response body through one of the following:spring-doc.cn

If the built-in assertions are insufficient, you can consume the object instead and perform any other assertions:spring-doc.cn

client.get().uri("/persons/1")
		.exchange()
		.expectStatus().isOk()
		.expectBody(Person.class)
		.consumeWith(result -> {
			// custom assertions (for example, AssertJ)...
		});
client.get().uri("/persons/1")
	.exchange()
	.expectStatus().isOk()
	.expectBody<Person>()
	.consumeWith {
		// custom assertions (for example, AssertJ)...
	}

Or you can exit the workflow and obtain a EntityExchangeResult:spring-doc.cn

EntityExchangeResult<Person> result = client.get().uri("/persons/1")
		.exchange()
		.expectStatus().isOk()
		.expectBody(Person.class)
		.returnResult();

Person person = result.getResponseBody();
HttpHeaders requestHeaders = result.getRequestHeaders();
val result = client.get().uri("/persons/1")
	.exchange()
	.expectStatus().isOk
	.expectBody<Person>()
	.returnResult()

val person: Person? = result.responseBody
val requestHeaders = result.responseHeaders
When you need to decode to a target type with generics, look for the overloaded methods that accept ParameterizedTypeReference instead of Class<T>.

No Content

If the response is not expected to have content, you can assert that as follows:spring-doc.cn

client.post().uri("/persons")
		.body(person)
		.exchange()
		.expectStatus().isCreated()
		.expectBody().isEmpty();
client.post().uri("/persons")
	.body(person)
	.exchange()
	.expectStatus().isCreated()
	.expectBody().isEmpty()

If you want to ignore the response content, the following releases the content without any assertions:spring-doc.cn

client.post().uri("/persons")
		.body(person)
		.exchange()
		.expectStatus().isCreated()
		.expectBody(Void.class);
client.get().uri("/persons/123")
	.exchange()
	.expectStatus().isNotFound
	.expectBody<Unit>()
Consuming the response body (for example, with expectBody) is required if your tests are running with leak detection for pooled buffers. Without that, the tool will report buffers being leaked.

JSON Content

You can use expectBody() without a target type to perform assertions on the raw content rather than through higher level Object(s).spring-doc.cn

To verify the full JSON content with JSONAssert:spring-doc.cn

client.get().uri("/persons/1")
		.exchange()
		.expectStatus().isOk()
		.expectBody()
		.json("{\"name\":\"Jane\"}");
client.get().uri("/persons/1")
	.exchange()
	.expectStatus().isOk()
	.expectBody()
	.json("{\"name\":\"Jane\"}")

To verify JSON content with JSONPath:spring-doc.cn

client.get().uri("/persons")
		.exchange()
		.expectStatus().isOk()
		.expectBody()
		.jsonPath("$[0].name").isEqualTo("Jane")
		.jsonPath("$[1].name").isEqualTo("Jason");
client.get().uri("/persons")
	.exchange()
	.expectStatus().isOk()
	.expectBody()
	.jsonPath("$[0].name").isEqualTo("Jane")
	.jsonPath("$[1].name").isEqualTo("Jason")

AssertJ Integration

RestTestClientResponse is the main entry point for the AssertJ integration. It is an AssertProvider that wraps the ResponseSpec of an exchange in order to enable use of assertThat() statements. For example:spring-doc.cn

RestTestClient.ResponseSpec spec = client.get().uri("/persons").exchange();

RestTestClientResponse response = RestTestClientResponse.from(spec);
assertThat(response).hasStatusOk();
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN);
val spec = client.get().uri("/persons").exchange()

val response = RestTestClientResponse.from(spec)
Assertions.assertThat(response).hasStatusOk()
Assertions.assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN)

You can also use the built-in workflow first, and then obtain an ExchangeResult to wrap and continue with AssertJ. For example:spring-doc.cn

ExchangeResult result = client.get().uri("/persons").exchange().returnResult();

RestTestClientResponse response = RestTestClientResponse.from(result);
assertThat(response).hasStatusOk();
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN);
val result = client.get().uri("/persons").exchange().returnResult()

val response = RestTestClientResponse.from(result)
Assertions.assertThat(response).hasStatusOk()
Assertions.assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN)