Migrate Structure
This commit is contained in:
1930
framework-docs/modules/ROOT/pages/testing/annotations.adoc
Normal file
1930
framework-docs/modules/ROOT/pages/testing/annotations.adoc
Normal file
File diff suppressed because it is too large
Load Diff
6
framework-docs/modules/ROOT/pages/testing/appendix.adoc
Normal file
6
framework-docs/modules/ROOT/pages/testing/appendix.adoc
Normal file
@@ -0,0 +1,6 @@
|
||||
[[testing.appendix]]
|
||||
= Appendix
|
||||
|
||||
include::annotations.adoc[leveloffset=+1]
|
||||
|
||||
include::resources.adoc[leveloffset=+1]
|
||||
155
framework-docs/modules/ROOT/pages/testing/integration.adoc
Normal file
155
framework-docs/modules/ROOT/pages/testing/integration.adoc
Normal file
@@ -0,0 +1,155 @@
|
||||
[[integration-testing]]
|
||||
= Integration Testing
|
||||
|
||||
It is important to be able to perform some integration testing without requiring
|
||||
deployment to your application server or connecting to other enterprise infrastructure.
|
||||
Doing so lets you test things such as:
|
||||
|
||||
* The correct wiring of your Spring IoC container contexts.
|
||||
* Data access using JDBC or an ORM tool. This can include such things as the correctness
|
||||
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
|
||||
|
||||
The Spring Framework provides first-class support for integration testing in the
|
||||
`spring-test` module. The name of the actual JAR file might include the release version
|
||||
and might also be in the long `org.springframework.test` form, depending on where you get
|
||||
it from (see the <<core.adoc#beans-dependencies, section on Dependency Management>>
|
||||
for an explanation). This library includes the `org.springframework.test` package, which
|
||||
contains valuable classes for integration testing with a Spring container. This testing
|
||||
does not rely on an application server or other deployment environment. Such tests are
|
||||
slower to run than unit tests but much faster than the equivalent Selenium tests or
|
||||
remote tests that rely on deployment to an application server.
|
||||
|
||||
Unit and integration testing support is provided in the form of the annotation-driven
|
||||
<<testcontext-framework, Spring TestContext Framework>>. The TestContext framework is
|
||||
agnostic of the actual testing framework in use, which allows instrumentation of tests
|
||||
in various environments, including JUnit, TestNG, and others.
|
||||
|
||||
The following section provides an overview of the high-level goals of Spring's
|
||||
integration support, and the rest of this chapter then focuses on dedicated topics:
|
||||
|
||||
* <<integration-testing-support-jdbc>>
|
||||
* <<testcontext-framework>>
|
||||
* <<webtestclient>>
|
||||
* <<spring-mvc-test-framework>>
|
||||
* <<spring-mvc-test-client>>
|
||||
* <<integration-testing-annotations>>
|
||||
|
||||
|
||||
|
||||
[[integration-testing-goals]]
|
||||
== Goals of Integration Testing
|
||||
|
||||
Spring's integration testing support has the following primary goals:
|
||||
|
||||
* To manage <<testing-ctx-management, Spring IoC container caching>> between tests.
|
||||
* To provide <<testing-fixture-di, Dependency Injection of test fixture instances>>.
|
||||
* To provide <<testing-tx, transaction management>> appropriate to integration testing.
|
||||
* To supply <<testing-support-classes, Spring-specific base classes>> that assist
|
||||
developers in writing integration tests.
|
||||
|
||||
The next few sections describe each goal and provide links to implementation and
|
||||
configuration details.
|
||||
|
||||
|
||||
[[testing-ctx-management]]
|
||||
=== Context Management and Caching
|
||||
|
||||
The Spring TestContext Framework provides consistent loading of Spring
|
||||
`ApplicationContext` instances and `WebApplicationContext` instances as well as caching
|
||||
of those contexts. Support for the caching of loaded contexts is important, because
|
||||
startup time can become an issue -- not because of the overhead of Spring itself, but
|
||||
because the objects instantiated by the Spring container take time to instantiate. For
|
||||
example, a project with 50 to 100 Hibernate mapping files might take 10 to 20 seconds to
|
||||
load the mapping files, and incurring that cost before running every test in every test
|
||||
fixture leads to slower overall test runs that reduce developer productivity.
|
||||
|
||||
Test classes typically declare either an array of resource locations for XML or Groovy
|
||||
configuration metadata -- often in the classpath -- or an array of component classes that
|
||||
is used to configure the application. These locations or classes are the same as or
|
||||
similar to those specified in `web.xml` or other configuration files for production
|
||||
deployments.
|
||||
|
||||
By default, once loaded, the configured `ApplicationContext` is reused for each test.
|
||||
Thus, the setup cost is incurred only once per test suite, and subsequent test execution
|
||||
is much faster. In this context, the term "`test suite`" means all tests run in the same
|
||||
JVM -- for example, all tests run from an Ant, Maven, or Gradle build for a given project
|
||||
or module. In the unlikely case that a test corrupts the application context and requires
|
||||
reloading (for example, by modifying a bean definition or the state of an application
|
||||
object) the TestContext framework can be configured to reload the configuration and
|
||||
rebuild the application context before executing the next test.
|
||||
|
||||
See <<testcontext-ctx-management>> and <<testcontext-ctx-management-caching>> with the
|
||||
TestContext framework.
|
||||
|
||||
|
||||
[[testing-fixture-di]]
|
||||
=== Dependency Injection of Test Fixtures
|
||||
|
||||
When the TestContext framework loads your application context, it can optionally
|
||||
configure instances of your test classes by using Dependency Injection. This provides a
|
||||
convenient mechanism for setting up test fixtures by using preconfigured beans from your
|
||||
application context. A strong benefit here is that you can reuse application contexts
|
||||
across various testing scenarios (for example, for configuring Spring-managed object
|
||||
graphs, transactional proxies, `DataSource` instances, and others), thus avoiding the
|
||||
need to duplicate complex test fixture setup for individual test cases.
|
||||
|
||||
As an example, consider a scenario where we have a class (`HibernateTitleRepository`)
|
||||
that implements data access logic for a `Title` domain entity. We want to write
|
||||
integration tests that test the following areas:
|
||||
|
||||
* The Spring configuration: Basically, is everything related to the configuration of the
|
||||
`HibernateTitleRepository` bean correct and present?
|
||||
* The Hibernate mapping file configuration: Is everything mapped correctly and are the
|
||||
correct lazy-loading settings in place?
|
||||
* The logic of the `HibernateTitleRepository`: Does the configured instance of this class
|
||||
perform as anticipated?
|
||||
|
||||
See dependency injection of test fixtures with the
|
||||
<<testcontext-fixture-di, TestContext framework>>.
|
||||
|
||||
|
||||
[[testing-tx]]
|
||||
=== Transaction Management
|
||||
|
||||
One common issue in tests that access a real database is their effect on the state of the
|
||||
persistence store. Even when you use a development database, changes to the state may
|
||||
affect future tests. Also, many operations -- such as inserting or modifying persistent
|
||||
data -- cannot be performed (or verified) outside of a transaction.
|
||||
|
||||
The TestContext framework addresses this issue. By default, the framework creates and
|
||||
rolls back a transaction for each test. You can write code that can assume the existence
|
||||
of a transaction. If you call transactionally proxied objects in your tests, they behave
|
||||
correctly, according to their configured transactional semantics. In addition, if a test
|
||||
method deletes the contents of selected tables while running within the transaction
|
||||
managed for the test, the transaction rolls back by default, and the database returns to
|
||||
its state prior to execution of the test. Transactional support is provided to a test by
|
||||
using a `PlatformTransactionManager` bean defined in the test's application context.
|
||||
|
||||
If you want a transaction to commit (unusual, but occasionally useful when you want a
|
||||
particular test to populate or modify the database), you can tell the TestContext
|
||||
framework to cause the transaction to commit instead of roll back by using the
|
||||
<<integration-testing-annotations, `@Commit`>> annotation.
|
||||
|
||||
See transaction management with the <<testcontext-tx, TestContext framework>>.
|
||||
|
||||
|
||||
[[testing-support-classes]]
|
||||
=== Support Classes for Integration Testing
|
||||
|
||||
The Spring TestContext Framework provides several `abstract` support classes that
|
||||
simplify the writing of integration tests. These base test classes provide well-defined
|
||||
hooks into the testing framework as well as convenient instance variables and methods,
|
||||
which let you access:
|
||||
|
||||
* The `ApplicationContext`, for performing explicit bean lookups or testing the state of
|
||||
the context as a whole.
|
||||
* A `JdbcTemplate`, for executing SQL statements to query the database. You can use such
|
||||
queries to confirm database state both before and after execution of database-related
|
||||
application code, and Spring ensures that such queries run in the scope of the same
|
||||
transaction as the application code. When used in conjunction with an ORM tool, be sure
|
||||
to avoid <<testcontext-tx-false-positives, false positives>>.
|
||||
|
||||
In addition, you may want to create your own custom, application-wide superclass with
|
||||
instance variables and methods specific to your project.
|
||||
|
||||
See support classes for the <<testcontext-support-classes, TestContext framework>>.
|
||||
@@ -0,0 +1,8 @@
|
||||
[[testing-introduction]]
|
||||
= Introduction to Spring Testing
|
||||
|
||||
Testing is an integral part of enterprise software development. This chapter focuses on
|
||||
the value added by the IoC principle to <<unit-testing, unit testing>> and on the benefits
|
||||
of the Spring Framework's support for <<integration-testing, integration testing>>. (A
|
||||
thorough treatment of testing in the enterprise is beyond the scope of this reference
|
||||
manual.)
|
||||
32
framework-docs/modules/ROOT/pages/testing/resources.adoc
Normal file
32
framework-docs/modules/ROOT/pages/testing/resources.adoc
Normal file
@@ -0,0 +1,32 @@
|
||||
[[testing-resources]]
|
||||
= Further Resources
|
||||
See the following resources for more information about testing:
|
||||
|
||||
* https://www.junit.org/[JUnit]: "A programmer-friendly testing framework for Java and the JVM".
|
||||
Used by the Spring Framework in its test suite and supported in the
|
||||
<<testcontext-framework, Spring TestContext Framework>>.
|
||||
* https://testng.org/[TestNG]: A testing framework inspired by JUnit with added support
|
||||
for test groups, data-driven testing, distributed testing, and other features. Supported
|
||||
in the <<testcontext-framework, Spring TestContext Framework>>
|
||||
* https://assertj.github.io/doc/[AssertJ]: "Fluent assertions for Java",
|
||||
including support for Java 8 lambdas, streams, and numerous other features.
|
||||
* https://en.wikipedia.org/wiki/Mock_Object[Mock Objects]: Article in Wikipedia.
|
||||
* http://www.mockobjects.com/[MockObjects.com]: Web site dedicated to mock objects, a
|
||||
technique for improving the design of code within test-driven development.
|
||||
* https://mockito.github.io[Mockito]: Java mock library based on the
|
||||
http://xunitpatterns.com/Test%20Spy.html[Test Spy] pattern. Used by the Spring Framework
|
||||
in its test suite.
|
||||
* https://easymock.org/[EasyMock]: Java library "that provides Mock Objects for
|
||||
interfaces (and objects through the class extension) by generating them on the fly using
|
||||
Java's proxy mechanism."
|
||||
* https://jmock.org/[JMock]: Library that supports test-driven development of Java code
|
||||
with mock objects.
|
||||
* https://www.dbunit.org/[DbUnit]: JUnit extension (also usable with Ant and Maven) that
|
||||
is targeted at database-driven projects and, among other things, puts your database into
|
||||
a known state between test runs.
|
||||
* https://www.testcontainers.org/[Testcontainers]: Java library that supports JUnit
|
||||
tests, providing lightweight, throwaway instances of common databases, Selenium web
|
||||
browsers, or anything else that can run in a Docker container.
|
||||
* https://sourceforge.net/projects/grinder/[The Grinder]: Java load testing framework.
|
||||
* https://github.com/Ninja-Squad/springmockk[SpringMockK]: Support for Spring Boot
|
||||
integration tests written in Kotlin using https://mockk.io/[MockK] instead of Mockito.
|
||||
@@ -0,0 +1,185 @@
|
||||
[[spring-mvc-test-client]]
|
||||
= Testing Client Applications
|
||||
|
||||
You can use client-side tests to test code that internally uses the `RestTemplate`. The
|
||||
idea is to declare expected requests and to provide "`stub`" responses so that you can
|
||||
focus on testing the code in isolation (that is, without running a server). The following
|
||||
example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
|
||||
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
|
||||
mockServer.verify();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val restTemplate = RestTemplate()
|
||||
|
||||
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
|
||||
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
|
||||
mockServer.verify()
|
||||
----
|
||||
|
||||
In the preceding example, `MockRestServiceServer` (the central class for client-side REST
|
||||
tests) configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that
|
||||
asserts actual requests against expectations and returns "`stub`" responses. In this
|
||||
case, we expect a request to `/greeting` and want to return a 200 response with
|
||||
`text/plain` content. We can define additional expected requests and stub responses as
|
||||
needed. When we define expected requests and stub responses, the `RestTemplate` can be
|
||||
used in client-side code as usual. At the end of testing, `mockServer.verify()` can be
|
||||
used to verify that all expectations have been satisfied.
|
||||
|
||||
By default, requests are expected in the order in which expectations were declared. You
|
||||
can set the `ignoreExpectOrder` option when building the server, in which case all
|
||||
expectations are checked (in order) to find a match for a given request. That means
|
||||
requests are allowed to come in any order. The following example uses `ignoreExpectOrder`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()
|
||||
----
|
||||
|
||||
Even with unordered requests by default, each request is allowed to run once only.
|
||||
The `expect` method provides an overloaded variant that accepts an `ExpectedCount`
|
||||
argument that specifies a count range (for example, `once`, `manyTimes`, `max`, `min`,
|
||||
`between`, and so on). The following example uses `times`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
|
||||
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
|
||||
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());
|
||||
|
||||
// ...
|
||||
|
||||
mockServer.verify();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val restTemplate = RestTemplate()
|
||||
|
||||
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
|
||||
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
|
||||
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())
|
||||
|
||||
// ...
|
||||
|
||||
mockServer.verify()
|
||||
----
|
||||
|
||||
Note that, when `ignoreExpectOrder` is not set (the default), and, therefore, requests
|
||||
are expected in order of declaration, then that order applies only to the first of any
|
||||
expected request. For example if "/something" is expected two times followed by
|
||||
"/somewhere" three times, then there should be a request to "/something" before there is
|
||||
a request to "/somewhere", but, aside from that subsequent "/something" and "/somewhere",
|
||||
requests can come at any time.
|
||||
|
||||
As an alternative to all of the above, the client-side test support also provides a
|
||||
`ClientHttpRequestFactory` implementation that you can configure into a `RestTemplate` to
|
||||
bind it to a `MockMvc` instance. That allows processing requests using actual server-side
|
||||
logic but without running a server. The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
|
||||
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
----
|
||||
|
||||
In some cases it may be necessary to perform an actual call to a remote service instead
|
||||
of mocking the response. The following example shows how to do that through
|
||||
`ExecutingResponseCreator`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
// Create ExecutingResponseCreator with the original request factory
|
||||
ExecutingResponseCreator withActualResponse = new ExecutingResponseCreator(restTemplate.getRequestFactory());
|
||||
|
||||
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
|
||||
mockServer.expect(requestTo("/profile")).andRespond(withSuccess());
|
||||
mockServer.expect(requestTo("/quoteOfTheDay")).andRespond(withActualResponse);
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
|
||||
mockServer.verify();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val restTemplate = RestTemplate()
|
||||
|
||||
// Create ExecutingResponseCreator with the original request factory
|
||||
val withActualResponse = new ExecutingResponseCreator(restTemplate.getRequestFactory())
|
||||
|
||||
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
|
||||
mockServer.expect(requestTo("/profile")).andRespond(withSuccess())
|
||||
mockServer.expect(requestTo("/quoteOfTheDay")).andRespond(withActualResponse)
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
|
||||
mockServer.verify()
|
||||
----
|
||||
|
||||
In the preceding example, we create the `ExecutingResponseCreator` using the
|
||||
`ClientHttpRequestFactory` from the `RestTemplate` _before_ `MockRestServiceServer` replaces
|
||||
it with a different one that mocks responses.
|
||||
Then we define expectations with two kinds of responses:
|
||||
|
||||
* a stub `200` response for the `/profile` endpoint (no actual request will be executed)
|
||||
* a response obtained through a call to the `/quoteOfTheDay` endpoint
|
||||
|
||||
In the second case, the request is executed through the `ClientHttpRequestFactory` that was
|
||||
captured earlier. This generates a response that could e.g. come from an actual remote server,
|
||||
depending on how the `RestTemplate` was originally configured.
|
||||
|
||||
[[spring-mvc-test-client-static-imports]]
|
||||
== Static Imports
|
||||
|
||||
As with server-side tests, the fluent API for client-side tests requires a few static
|
||||
imports. Those are easy to find by searching for `MockRest*`. Eclipse users should add
|
||||
`MockRestRequestMatchers.{asterisk}` and `MockRestResponseCreators.{asterisk}` as
|
||||
"`favorite static members`" in the Eclipse preferences under Java -> Editor -> Content
|
||||
Assist -> Favorites. That allows using content assist after typing the first character of
|
||||
the static method name. Other IDEs (such IntelliJ) may not require any additional
|
||||
configuration. Check for the support for code completion on static members.
|
||||
|
||||
[[spring-mvc-test-client-resources]]
|
||||
== Further Examples of Client-side REST Tests
|
||||
|
||||
Spring MVC Test's own tests include
|
||||
{spring-framework-main-code}/spring-test/src/test/java/org/springframework/test/web/client/samples[example
|
||||
tests] of client-side REST tests.
|
||||
File diff suppressed because it is too large
Load Diff
35
framework-docs/modules/ROOT/pages/testing/support-jdbc.adoc
Normal file
35
framework-docs/modules/ROOT/pages/testing/support-jdbc.adoc
Normal file
@@ -0,0 +1,35 @@
|
||||
[[integration-testing-support-jdbc]]
|
||||
= JDBC Testing Support
|
||||
|
||||
[[integration-testing-support-jdbc-test-utils]]
|
||||
== JdbcTestUtils
|
||||
|
||||
The `org.springframework.test.jdbc` package contains `JdbcTestUtils`, which is a
|
||||
collection of JDBC-related utility functions intended to simplify standard database
|
||||
testing scenarios. Specifically, `JdbcTestUtils` provides the following static utility
|
||||
methods.
|
||||
|
||||
* `countRowsInTable(..)`: Counts the number of rows in the given table.
|
||||
* `countRowsInTableWhere(..)`: Counts the number of rows in the given table by using the
|
||||
provided `WHERE` clause.
|
||||
* `deleteFromTables(..)`: Deletes all rows from the specified tables.
|
||||
* `deleteFromTableWhere(..)`: Deletes rows from the given table by using the provided
|
||||
`WHERE` clause.
|
||||
* `dropTables(..)`: Drops the specified tables.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
<<testcontext-support-classes-junit4, `AbstractTransactionalJUnit4SpringContextTests`>>
|
||||
and <<testcontext-support-classes-testng, `AbstractTransactionalTestNGSpringContextTests`>>
|
||||
provide convenience methods that delegate to the aforementioned methods in
|
||||
`JdbcTestUtils`.
|
||||
====
|
||||
|
||||
[[integration-testing-support-jdbc-embedded-database]]
|
||||
== Embedded Databases
|
||||
|
||||
The `spring-jdbc` module provides support for configuring and launching an embedded
|
||||
database, which you can use in integration tests that interact with a database.
|
||||
For details, see <<data-access.adoc#jdbc-embedded-database-support, Embedded Database
|
||||
Support>> and <<data-access.adoc#jdbc-embedded-database-dao-testing, Testing Data Access
|
||||
Logic with an Embedded Database>>.
|
||||
4725
framework-docs/modules/ROOT/pages/testing/testcontext-framework.adoc
Normal file
4725
framework-docs/modules/ROOT/pages/testing/testcontext-framework.adoc
Normal file
File diff suppressed because it is too large
Load Diff
168
framework-docs/modules/ROOT/pages/testing/unit.adoc
Normal file
168
framework-docs/modules/ROOT/pages/testing/unit.adoc
Normal file
@@ -0,0 +1,168 @@
|
||||
[[unit-testing]]
|
||||
= Unit Testing
|
||||
|
||||
Dependency injection should make your code less dependent on the container than it would
|
||||
be with traditional J2EE / Java EE development. The POJOs that make up your application
|
||||
should be testable in JUnit or TestNG tests, with objects instantiated by using the `new`
|
||||
operator, without Spring or any other container. You can use <<mock-objects, mock objects>>
|
||||
(in conjunction with other valuable testing techniques) to test your code in isolation.
|
||||
If you follow the architecture recommendations for Spring, the resulting clean layering
|
||||
and componentization of your codebase facilitate easier unit testing. For example,
|
||||
you can test service layer objects by stubbing or mocking DAO or repository interfaces,
|
||||
without needing to access persistent data while running unit tests.
|
||||
|
||||
True unit tests typically run extremely quickly, as there is no runtime infrastructure to
|
||||
set up. Emphasizing true unit tests as part of your development methodology can boost
|
||||
your productivity. You may not need this section of the testing chapter to help you write
|
||||
effective unit tests for your IoC-based applications. For certain unit testing scenarios,
|
||||
however, the Spring Framework provides mock objects and testing support classes, which
|
||||
are described in this chapter.
|
||||
|
||||
|
||||
|
||||
[[mock-objects]]
|
||||
== Mock Objects
|
||||
|
||||
Spring includes a number of packages dedicated to mocking:
|
||||
|
||||
* <<mock-objects-env>>
|
||||
* <<mock-objects-jndi>>
|
||||
* <<mock-objects-servlet>>
|
||||
* <<mock-objects-web-reactive>>
|
||||
|
||||
|
||||
[[mock-objects-env]]
|
||||
=== Environment
|
||||
|
||||
The `org.springframework.mock.env` package contains mock implementations of the
|
||||
`Environment` and `PropertySource` abstractions (see
|
||||
<<core.adoc#beans-definition-profiles, Bean Definition Profiles>>
|
||||
and <<core.adoc#beans-property-source-abstraction, `PropertySource` Abstraction>>).
|
||||
`MockEnvironment` and `MockPropertySource` are useful for developing
|
||||
out-of-container tests for code that depends on environment-specific properties.
|
||||
|
||||
|
||||
[[mock-objects-jndi]]
|
||||
=== JNDI
|
||||
|
||||
The `org.springframework.mock.jndi` package contains a partial implementation of the JNDI
|
||||
SPI, which you can use to set up a simple JNDI environment for test suites or stand-alone
|
||||
applications. If, for example, JDBC `DataSource` instances get bound to the same JNDI
|
||||
names in test code as they do in a Jakarta EE container, you can reuse both application code
|
||||
and configuration in testing scenarios without modification.
|
||||
|
||||
WARNING: The mock JNDI support in the `org.springframework.mock.jndi` package is
|
||||
officially deprecated as of Spring Framework 5.2 in favor of complete solutions from third
|
||||
parties such as https://github.com/h-thurow/Simple-JNDI[Simple-JNDI].
|
||||
|
||||
|
||||
[[mock-objects-servlet]]
|
||||
=== Servlet API
|
||||
|
||||
The `org.springframework.mock.web` package contains a comprehensive set of Servlet API
|
||||
mock objects that are useful for testing web contexts, controllers, and filters. These
|
||||
mock objects are targeted at usage with Spring's Web MVC framework and are generally more
|
||||
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock])
|
||||
or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]).
|
||||
|
||||
TIP: Since Spring Framework 6.0, the mock objects in `org.springframework.mock.web` are
|
||||
based on the Servlet 6.0 API.
|
||||
|
||||
The Spring MVC Test framework builds on the mock Servlet API objects to provide an
|
||||
integration testing framework for Spring MVC. See <<spring-mvc-test-framework>>.
|
||||
|
||||
|
||||
[[mock-objects-web-reactive]]
|
||||
=== Spring Web Reactive
|
||||
|
||||
The `org.springframework.mock.http.server.reactive` package contains mock implementations
|
||||
of `ServerHttpRequest` and `ServerHttpResponse` for use in WebFlux applications. The
|
||||
`org.springframework.mock.web.server` package contains a mock `ServerWebExchange` that
|
||||
depends on those mock request and response objects.
|
||||
|
||||
Both `MockServerHttpRequest` and `MockServerHttpResponse` extend from the same abstract
|
||||
base classes as server-specific implementations and share behavior with them. For
|
||||
example, a mock request is immutable once created, but you can use the `mutate()` method
|
||||
from `ServerHttpRequest` to create a modified instance.
|
||||
|
||||
In order for the mock response to properly implement the write contract and return a
|
||||
write completion handle (that is, `Mono<Void>`), it by default uses a `Flux` with
|
||||
`cache().then()`, which buffers the data and makes it available for assertions in tests.
|
||||
Applications can set a custom write function (for example, to test an infinite stream).
|
||||
|
||||
The <<webtestclient>> builds on the mock request and response to provide support for
|
||||
testing WebFlux applications without an HTTP server. The client can also be used for
|
||||
end-to-end tests with a running server.
|
||||
|
||||
|
||||
|
||||
[[unit-testing-support-classes]]
|
||||
== Unit Testing Support Classes
|
||||
|
||||
Spring includes a number of classes that can help with unit testing. They fall into two
|
||||
categories:
|
||||
|
||||
* <<unit-testing-utilities>>
|
||||
* <<unit-testing-spring-mvc>>
|
||||
|
||||
|
||||
[[unit-testing-utilities]]
|
||||
=== General Testing Utilities
|
||||
|
||||
The `org.springframework.test.util` package contains several general purpose utilities
|
||||
for use in unit and integration testing.
|
||||
|
||||
{api-spring-framework}/test/util/AopTestUtils.html[`AopTestUtils`] is a collection of
|
||||
AOP-related utility methods. You can use these methods to obtain a reference to the
|
||||
underlying target object hidden behind one or more Spring proxies. For example, if you
|
||||
have configured a bean as a dynamic mock by using a library such as EasyMock or Mockito,
|
||||
and the mock is wrapped in a Spring proxy, you may need direct access to the underlying
|
||||
mock to configure expectations on it and perform verifications. For Spring's core AOP
|
||||
utilities, see {api-spring-framework}/aop/support/AopUtils.html[`AopUtils`] and
|
||||
{api-spring-framework}/aop/framework/AopProxyUtils.html[`AopProxyUtils`].
|
||||
|
||||
{api-spring-framework}/test/util/ReflectionTestUtils.html[`ReflectionTestUtils`] is a
|
||||
collection of reflection-based utility methods. You can use these methods in testing
|
||||
scenarios where you need to change the value of a constant, set a non-`public` field,
|
||||
invoke a non-`public` setter method, or invoke a non-`public` configuration or lifecycle
|
||||
callback method when testing application code for use cases such as the following:
|
||||
|
||||
* ORM frameworks (such as JPA and Hibernate) that condone `private` or `protected` field
|
||||
access as opposed to `public` setter methods for properties in a domain entity.
|
||||
* Spring's support for annotations (such as `@Autowired`, `@Inject`, and `@Resource`),
|
||||
that provide dependency injection for `private` or `protected` fields, setter methods,
|
||||
and configuration methods.
|
||||
* Use of annotations such as `@PostConstruct` and `@PreDestroy` for lifecycle callback
|
||||
methods.
|
||||
|
||||
{api-spring-framework}/test/util/TestSocketUtils.html[`TestSocketUtils`] is a simple
|
||||
utility for finding available TCP ports on `localhost` for use in integration testing
|
||||
scenarios.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`TestSocketUtils` can be used in integration tests which start an external server on an
|
||||
available random port. However, these utilities make no guarantee about the subsequent
|
||||
availability of a given port and are therefore unreliable. Instead of using
|
||||
`TestSocketUtils` to find an available local port for a server, it is recommended that
|
||||
you rely on a server's ability to start on a random ephemeral port that it selects or is
|
||||
assigned by the operating system. To interact with that server, you should query the
|
||||
server for the port it is currently using.
|
||||
====
|
||||
|
||||
|
||||
[[unit-testing-spring-mvc]]
|
||||
=== Spring MVC Testing Utilities
|
||||
|
||||
The `org.springframework.test.web` package contains
|
||||
{api-spring-framework}/test/web/ModelAndViewAssert.html[`ModelAndViewAssert`], which you
|
||||
can use in combination with JUnit, TestNG, or any other testing framework for unit tests
|
||||
that deal with Spring MVC `ModelAndView` objects.
|
||||
|
||||
.Unit testing Spring MVC Controllers
|
||||
TIP: To unit test your Spring MVC `Controller` classes as POJOs, use `ModelAndViewAssert`
|
||||
combined with `MockHttpServletRequest`, `MockHttpSession`, and so on from Spring's
|
||||
<<mock-objects-servlet, Servlet API mocks>>. For thorough integration testing of your
|
||||
Spring MVC and REST `Controller` classes in conjunction with your `WebApplicationContext`
|
||||
configuration for Spring MVC, use the
|
||||
<<spring-mvc-test-framework, Spring MVC Test Framework>> instead.
|
||||
594
framework-docs/modules/ROOT/pages/testing/webtestclient.adoc
Normal file
594
framework-docs/modules/ROOT/pages/testing/webtestclient.adoc
Normal file
@@ -0,0 +1,594 @@
|
||||
[[webtestclient]]
|
||||
= WebTestClient
|
||||
|
||||
`WebTestClient` is an HTTP client designed for testing server applications. It wraps
|
||||
Spring's <<web-reactive.adoc#webflux-client, WebClient>> and uses it to perform requests
|
||||
but exposes a testing facade for verifying responses. `WebTestClient` can be used to
|
||||
perform end-to-end HTTP tests. It can also be used to test Spring MVC and Spring WebFlux
|
||||
applications without a running server via mock server request and response objects.
|
||||
|
||||
TIP: Kotlin users: See <<languages.adoc#kotlin-webtestclient-issue, this section>>
|
||||
related to use of the `WebTestClient`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webtestclient-setup]]
|
||||
== Setup
|
||||
|
||||
To set up a `WebTestClient` you need to choose a server setup to bind to. This can be one
|
||||
of several mock server setup choices or a connection to a live server.
|
||||
|
||||
|
||||
|
||||
[[webtestclient-controller-config]]
|
||||
=== Bind to Controller
|
||||
|
||||
This setup allows you to test specific controller(s) via mock request and response objects,
|
||||
without a running server.
|
||||
|
||||
For WebFlux applications, use the following which loads infrastructure equivalent to the
|
||||
<<web-reactive.adoc#webflux-config, WebFlux Java config>>, registers the given
|
||||
controller(s), and creates a <<web-reactive.adoc#webflux-web-handler-api, WebHandler chain>>
|
||||
to handle requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebTestClient client =
|
||||
WebTestClient.bindToController(new TestController()).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebTestClient.bindToController(TestController()).build()
|
||||
----
|
||||
|
||||
For Spring MVC, use the following which delegates to the
|
||||
{api-spring-framework}/test/web/servlet/setup/StandaloneMockMvcBuilder.html[StandaloneMockMvcBuilder]
|
||||
to load infrastructure equivalent to the <<web.adoc#mvc-config, WebMvc Java config>>,
|
||||
registers the given controller(s), and creates an instance of
|
||||
<<testing.adoc#spring-mvc-test-framework, MockMvc>> to handle requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebTestClient client =
|
||||
MockMvcWebTestClient.bindToController(new TestController()).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = MockMvcWebTestClient.bindToController(TestController()).build()
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webtestclient-context-config]]
|
||||
=== Bind to `ApplicationContext`
|
||||
|
||||
This setup allows you to load Spring configuration with Spring MVC or Spring WebFlux
|
||||
infrastructure and controller declarations and use it to handle requests via mock request
|
||||
and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
|
||||
{api-spring-framework}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
|
||||
to create the <<web-reactive.adoc#webflux-web-handler-api, WebHandler chain>> to handle
|
||||
requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@SpringJUnitConfig(WebConfig.class) // <1>
|
||||
class MyTests {
|
||||
|
||||
WebTestClient client;
|
||||
|
||||
@BeforeEach
|
||||
void setUp(ApplicationContext context) { // <2>
|
||||
client = WebTestClient.bindToApplicationContext(context).build(); // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig(WebConfig::class) // <1>
|
||||
class MyTests {
|
||||
|
||||
lateinit var client: WebTestClient
|
||||
|
||||
@BeforeEach
|
||||
fun setUp(context: ApplicationContext) { // <2>
|
||||
client = WebTestClient.bindToApplicationContext(context).build() // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
For Spring MVC, use the following where the Spring `ApplicationContext` is passed to
|
||||
{api-spring-framework}/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup-org.springframework.web.context.WebApplicationContext-[MockMvcBuilders.webAppContextSetup]
|
||||
to create a <<testing.adoc#spring-mvc-test-framework, MockMvc>> instance to handle
|
||||
requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration("classpath:META-INF/web-resources") // <1>
|
||||
@ContextHierarchy({
|
||||
@ContextConfiguration(classes = RootConfig.class),
|
||||
@ContextConfiguration(classes = WebConfig.class)
|
||||
})
|
||||
class MyTests {
|
||||
|
||||
@Autowired
|
||||
WebApplicationContext wac; // <2>
|
||||
|
||||
WebTestClient client;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration("classpath:META-INF/web-resources") // <1>
|
||||
@ContextHierarchy({
|
||||
@ContextConfiguration(classes = RootConfig.class),
|
||||
@ContextConfiguration(classes = WebConfig.class)
|
||||
})
|
||||
class MyTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var wac: WebApplicationContext; // <2>
|
||||
|
||||
lateinit var client: WebTestClient
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() { // <2>
|
||||
client = MockMvcWebTestClient.bindToApplicationContext(wac).build() // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
|
||||
|
||||
[[webtestclient-fn-config]]
|
||||
=== Bind to Router Function
|
||||
|
||||
This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
|
||||
mock request and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
|
||||
create a server setup to handle requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<?> route = ...
|
||||
client = WebTestClient.bindToRouterFunction(route).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route: RouterFunction<*> = ...
|
||||
val client = WebTestClient.bindToRouterFunction(route).build()
|
||||
----
|
||||
|
||||
For Spring MVC there are currently no options to test
|
||||
<<web.adoc#webmvc-fn, WebMvc functional endpoints>>.
|
||||
|
||||
|
||||
|
||||
[[webtestclient-server-config]]
|
||||
=== Bind to Server
|
||||
|
||||
This setup connects to a running server to perform full, end-to-end HTTP tests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webtestclient-client-config]]
|
||||
=== 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 `bindToServer()`. For all other configuration options,
|
||||
you need to use `configureClient()` to transition from server to client configuration, as
|
||||
follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client = WebTestClient.bindToController(new TestController())
|
||||
.configureClient()
|
||||
.baseUrl("/test")
|
||||
.build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client = WebTestClient.bindToController(TestController())
|
||||
.configureClient()
|
||||
.baseUrl("/test")
|
||||
.build()
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[webtestclient-tests]]
|
||||
== Writing Tests
|
||||
|
||||
`WebTestClient` provides an API identical to <<web-reactive.adoc#webflux-client, WebClient>>
|
||||
up to the point of performing a request by using `exchange()`. See the
|
||||
<<web-reactive.adoc#webflux-client-body, WebClient>> documentation for examples on how to
|
||||
prepare a request with any content including form data, multipart data, and more.
|
||||
|
||||
After the call to `exchange()`, `WebTestClient` diverges from the `WebClient` and
|
||||
instead continues with a workflow to verify responses.
|
||||
|
||||
To assert the response status and headers, use the following:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
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.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
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.
|
||||
* `expectBodyList(Class<T>)`: Decode and collect objects to `List<T>`.
|
||||
* `expectBody()`: Decode to `byte[]` for <<webtestclient-json>> or an empty body.
|
||||
|
||||
And perform assertions on the resulting higher level Object(s):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBodyList(Person.class).hasSize(3).contains(person);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBodyList
|
||||
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBodyList<Person>().hasSize(3).contains(person)
|
||||
----
|
||||
|
||||
If the built-in assertions are insufficient, you can consume the object instead and
|
||||
perform any other assertions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.consumeWith(result -> {
|
||||
// custom assertions (e.g. AssertJ)...
|
||||
});
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody<Person>()
|
||||
.consumeWith {
|
||||
// custom assertions (e.g. AssertJ)...
|
||||
}
|
||||
----
|
||||
|
||||
Or you can exit the workflow and obtain an `EntityExchangeResult`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
|
||||
val result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<Person>()
|
||||
.returnResult()
|
||||
----
|
||||
|
||||
TIP: When you need to decode to a target type with generics, look for the overloaded methods
|
||||
that accept
|
||||
{api-spring-framework}/core/ParameterizedTypeReference.html[`ParameterizedTypeReference`]
|
||||
instead of `Class<T>`.
|
||||
|
||||
|
||||
|
||||
[[webtestclient-no-content]]
|
||||
=== No Content
|
||||
|
||||
If the response is not expected to have content, you can assert that as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.post().uri("/persons")
|
||||
.body(personMono, Person.class)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody().isEmpty();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.post().uri("/persons")
|
||||
.bodyValue(person)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody().isEmpty()
|
||||
----
|
||||
|
||||
If you want to ignore the response content, the following releases the content without
|
||||
any assertions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons/123")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound()
|
||||
.expectBody(Void.class);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons/123")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound
|
||||
.expectBody<Unit>()
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webtestclient-json]]
|
||||
=== 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 https://jsonassert.skyscreamer.org[JSONAssert]:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.json("{\"name\":\"Jane\"}")
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.json("{\"name\":\"Jane\"}")
|
||||
----
|
||||
|
||||
To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("$[0].name").isEqualTo("Jane")
|
||||
.jsonPath("$[1].name").isEqualTo("Jason");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("$[0].name").isEqualTo("Jane")
|
||||
.jsonPath("$[1].name").isEqualTo("Jason")
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webtestclient-stream]]
|
||||
=== Streaming Responses
|
||||
|
||||
To test potentially infinite streams such as `"text/event-stream"` or
|
||||
`"application/x-ndjson"`, start by verifying the response status and headers, and then
|
||||
obtain a `FluxExchangeResult`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
|
||||
.accept(TEXT_EVENT_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.returnResult(MyEvent.class);
|
||||
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.returnResult
|
||||
|
||||
val result = client.get().uri("/events")
|
||||
.accept(TEXT_EVENT_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.returnResult<MyEvent>()
|
||||
----
|
||||
|
||||
Now you're ready to consume the response stream with `StepVerifier` from `reactor-test`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Event> eventFlux = result.getResponseBody();
|
||||
|
||||
StepVerifier.create(eventFlux)
|
||||
.expectNext(person)
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith(p -> ...)
|
||||
.thenCancel()
|
||||
.verify();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val eventFlux = result.getResponseBody()
|
||||
|
||||
StepVerifier.create(eventFlux)
|
||||
.expectNext(person)
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith { p -> ... }
|
||||
.thenCancel()
|
||||
.verify()
|
||||
----
|
||||
|
||||
|
||||
[[webtestclient-mockmvc]]
|
||||
=== MockMvc Assertions
|
||||
|
||||
`WebTestClient` is an HTTP client and as such it can only verify what is in the client
|
||||
response including status, headers, and body.
|
||||
|
||||
When testing a Spring MVC application with a MockMvc server setup, you have the extra
|
||||
choice to perform further assertions on the server response. To do that start by
|
||||
obtaining an `ExchangeResult` after asserting the body:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// For a response with a body
|
||||
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
|
||||
// For a response without a body
|
||||
EntityExchangeResult<Void> result = client.get().uri("/path")
|
||||
.exchange()
|
||||
.expectBody().isEmpty();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// For a response with a body
|
||||
val result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
|
||||
// For a response without a body
|
||||
val result = client.get().uri("/path")
|
||||
.exchange()
|
||||
.expectBody().isEmpty();
|
||||
----
|
||||
|
||||
Then switch to MockMvc server response assertions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MockMvcWebTestClient.resultActionsFor(result)
|
||||
.andExpect(model().attribute("integer", 3))
|
||||
.andExpect(model().attribute("string", "a string value"));
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
MockMvcWebTestClient.resultActionsFor(result)
|
||||
.andExpect(model().attribute("integer", 3))
|
||||
.andExpect(model().attribute("string", "a string value"));
|
||||
----
|
||||
|
||||
Reference in New Issue
Block a user