This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.2.9!

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.

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.

Bind to Controller

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

  • Java

  • Kotlin

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.

  • Java

  • Kotlin

@SpringJUnitConfig(WebConfig.class) (1)
class MyTests {

	RestTestClient client;

	@BeforeEach
	void setUp(ApplicationContext context) {  (2)
		client = RestTestClient.bindToApplicationContext(context).build(); (3)
	}
}
1 Specify the configuration to load
2 Inject the configuration
3 Create the RestTestClient
@SpringJUnitConfig(WebConfig::class) (1)
class MyTests {

	lateinit var client: RestTestClient

	@BeforeEach
	fun setUp(context: ApplicationContext) { (2)
		client = RestTestClient.bindToApplicationContext(context).build() (3)
	}
}
1 Specify the configuration to load
2 Inject the configuration
3 Create the RestTestClient

Bind to Router Function

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

  • Java

  • Kotlin

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:

  • Java

  • Kotlin

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:

  • Java

  • Kotlin

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

Writing Tests

RestTestClient provides an API identical to RestClient up to the point of performing a request by using exchange().

After the call to exchange(), RestTestClient diverges from RestClient, and instead continues with a workflow to verify responses.

To assert the response status and headers, use the following:

  • Java

  • Kotlin

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

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.

  • Java

  • Kotlin

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:

  • expectBody(Class<T>): Decode to single object.

  • expectBody(): Decode to byte[] for JSON Content or an empty body.

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

  • Java

  • Kotlin

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:

  • Java

  • Kotlin

EntityExchangeResult<Person> result = client.get().uri("/persons/1")
		.exchange()
		.expectStatus().isOk()
		.expectBody(Person.class)
		.returnResult();
val result = client.get().uri("/persons/1")
		.exchange()
		.expectStatus().isOk
		.expectBody<Person>()
		.returnResult()
When you need to decode to a target type with generics, look for the overloaded methods that accepthttps://docs.spring.io/spring-framework/docs/7.0.0-SNAPSHOT/javadoc-api/org/springframework/core/ParameterizedTypeReference.html[ParameterizedTypeReference] instead of Class<T>.

No Content

If the response is not expected to have content, you can assert that as follows:

  • Java

  • Kotlin

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:

  • Java

  • Kotlin

client.get().uri("/persons/123")
		.exchange()
		.expectStatus().isNotFound()
		.expectBody(Void.class);
client.get().uri("/persons/123")
		.exchange()
		.expectStatus().isNotFound
		.expectBody<Unit>()

JSON Content

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

To verify the full JSON content with JSONAssert:

  • Java

  • Kotlin

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:

  • Java

  • Kotlin

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")