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 tobyte[]
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")