2015-03-03 18:38:01 +08:00
|
|
|
|
[[testing]]
|
|
|
|
|
= Testing
|
2017-01-05 00:51:58 +08:00
|
|
|
|
:doc-root: https://docs.spring.io
|
|
|
|
|
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
|
|
|
|
|
:doc-spring-boot: {doc-root}/spring-boot/docs/current/reference
|
2017-03-29 20:20:12 +08:00
|
|
|
|
:toc: left
|
2017-10-06 10:23:38 +08:00
|
|
|
|
:toclevels: 4
|
2017-11-21 05:28:00 +08:00
|
|
|
|
:tabsize: 4
|
2017-09-18 18:16:47 +08:00
|
|
|
|
:docinfo1:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
This chapter covers Spring's support for integration testing and best practices for unit
|
2018-09-18 22:42:09 +08:00
|
|
|
|
testing. The Spring team advocates test-driven development (TDD). The Spring team has
|
|
|
|
|
found that the correct use of inversion of control (IoC) certainly does make both unit
|
|
|
|
|
and integration testing easier (in that the presence of setter methods and appropriate
|
2015-03-03 18:38:01 +08:00
|
|
|
|
constructors on classes makes them easier to wire together in a test without having to
|
2018-08-30 23:29:17 +08:00
|
|
|
|
set up service locator registries and similar structures).
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testing-introduction]]
|
|
|
|
|
== Introduction to Spring Testing
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
Testing is an integral part of enterprise software development. This chapter focuses on
|
2019-03-05 20:08:34 +08:00
|
|
|
|
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
|
2015-03-03 18:38:01 +08:00
|
|
|
|
thorough treatment of testing in the enterprise is beyond the scope of this reference
|
2018-08-30 23:29:17 +08:00
|
|
|
|
manual.)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[unit-testing]]
|
|
|
|
|
== Unit Testing
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Dependency injection should make your code less dependent on the container than it would
|
2015-03-03 18:38:01 +08:00
|
|
|
|
be with traditional Java EE development. The POJOs that make up your application should
|
2018-08-30 23:29:17 +08:00
|
|
|
|
be testable in JUnit or TestNG tests, with objects instantiated by using the `new`
|
2019-03-05 20:08:34 +08:00
|
|
|
|
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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[mock-objects]]
|
|
|
|
|
=== Mock Objects
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Spring includes a number of packages dedicated to mocking:
|
|
|
|
|
|
|
|
|
|
* <<mock-objects-env>>
|
|
|
|
|
* <<mock-objects-jndi>>
|
|
|
|
|
* <<mock-objects-servlet>>
|
|
|
|
|
* <<mock-objects-web-reactive>>
|
|
|
|
|
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[mock-objects-env]]
|
|
|
|
|
==== Environment
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The `org.springframework.mock.env` package contains mock implementations of the
|
2017-03-29 20:20:12 +08:00
|
|
|
|
`Environment` and `PropertySource` abstractions (see
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<core.adoc#beans-definition-profiles, Bean Definition Profiles>>
|
|
|
|
|
and <<core.adoc#beans-property-source-abstraction, `PropertySource` Abstraction>>).
|
2017-03-29 20:20:12 +08:00
|
|
|
|
`MockEnvironment` and `MockPropertySource` are useful for developing
|
2018-08-30 23:29:17 +08:00
|
|
|
|
out-of-container tests for code that depends on environment-specific properties.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[mock-objects-jndi]]
|
|
|
|
|
==== JNDI
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2019-04-11 22:46:50 +08:00
|
|
|
|
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
|
2018-09-18 22:42:09 +08:00
|
|
|
|
applications. If, for example, JDBC `DataSource` instances get bound to the same JNDI
|
|
|
|
|
names in test code as they do in a Java EE container, you can reuse both application code
|
|
|
|
|
and configuration in testing scenarios without modification.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-04-11 22:46:50 +08:00
|
|
|
|
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].
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[mock-objects-servlet]]
|
|
|
|
|
==== Servlet API
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The `org.springframework.mock.web` package contains a comprehensive set of Servlet API
|
2017-09-30 21:25:51 +08:00
|
|
|
|
mock objects that are useful for testing web contexts, controllers, and filters. These
|
2015-07-22 00:26:03 +08:00
|
|
|
|
mock objects are targeted at usage with Spring's Web MVC framework and are generally more
|
2019-03-21 06:48:14 +08:00
|
|
|
|
convenient to use than dynamic mock objects (such as http://easymock.org/[EasyMock])
|
2018-09-18 22:42:09 +08:00
|
|
|
|
or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]).
|
2015-07-22 00:26:03 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
TIP: Since Spring Framework 5.0, the mock objects in `org.springframework.mock.web` are
|
|
|
|
|
based on the Servlet 4.0 API.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-09-30 08:21:01 +08:00
|
|
|
|
The Spring MVC Test framework builds on the mock Servlet API objects to provide an
|
2018-09-18 22:42:09 +08:00
|
|
|
|
integration testing framework for Spring MVC. See <<spring-mvc-test-framework>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2017-09-30 08:21:01 +08:00
|
|
|
|
|
|
|
|
|
[[mock-objects-web-reactive]]
|
|
|
|
|
==== Spring Web Reactive
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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.
|
2017-09-30 08:21:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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
|
2017-10-03 21:56:13 +08:00
|
|
|
|
from `ServerHttpRequest` to create a modified instance.
|
|
|
|
|
|
|
|
|
|
In order for the mock response to properly implement the write contract and return a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
write completion handle (that is, `Mono<Void>`), it by default uses a `Flux` with
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`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).
|
2017-10-03 21:56:13 +08:00
|
|
|
|
|
|
|
|
|
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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[unit-testing-support-classes]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=== 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>>
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[unit-testing-utilities]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== General Testing Utilities
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-07-21 23:32:30 +08:00
|
|
|
|
The `org.springframework.test.util` package contains several general purpose utilities
|
|
|
|
|
for use in unit and integration testing.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`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
|
2015-07-22 00:24:52 +08:00
|
|
|
|
a non-`public` field, invoke a non-`public` setter method, or invoke a non-`public`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
configuration or lifecycle callback method when testing application code for use cases
|
|
|
|
|
such as the following:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* ORM frameworks (such as JPA and Hibernate) that condone `private` or `protected` field
|
2015-03-03 18:38:01 +08:00
|
|
|
|
access as opposed to `public` setter methods for properties in a domain entity.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* Spring's support for annotations (such as `@Autowired`, `@Inject`, and `@Resource`),
|
2018-09-18 22:42:09 +08:00
|
|
|
|
that provide dependency injection for `private` or `protected` fields, setter methods,
|
|
|
|
|
and configuration methods.
|
2015-07-21 23:10:00 +08:00
|
|
|
|
* Use of annotations such as `@PostConstruct` and `@PreDestroy` for lifecycle callback
|
|
|
|
|
methods.
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
{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`].
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-07-21 23:32:30 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[unit-testing-spring-mvc]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Spring MVC Testing Utilities
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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
|
2018-08-30 23:29:17 +08:00
|
|
|
|
that deal with Spring MVC `ModelAndView` objects.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Unit testing Spring MVC Controllers
|
2018-09-18 22:42:09 +08:00
|
|
|
|
TIP: To unit test your Spring MVC `Controller` classes as POJOs, use `ModelAndViewAssert`
|
|
|
|
|
combined with `MockHttpServletRequest`, `MockHttpSession`, and so on from Spring's
|
2015-07-22 00:26:03 +08:00
|
|
|
|
<<mock-objects-servlet, Servlet API mocks>>. For thorough integration testing of your
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Spring MVC and REST `Controller` classes in conjunction with your `WebApplicationContext`
|
2019-03-05 20:08:34 +08:00
|
|
|
|
configuration for Spring MVC, use the
|
|
|
|
|
<<spring-mvc-test-framework, Spring MVC Test Framework>> instead.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[integration-testing]]
|
|
|
|
|
== Integration Testing
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
This section (most of the rest of this chapter) covers integration testing for Spring
|
|
|
|
|
applications. It includes the following topics:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
* <<integration-testing-overview>>
|
|
|
|
|
* <<integration-testing-goals>>
|
|
|
|
|
* <<integration-testing-support-jdbc>>
|
|
|
|
|
* <<integration-testing-annotations>>
|
|
|
|
|
* <<testcontext-framework>>
|
|
|
|
|
* <<spring-mvc-test-framework>>
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[integration-testing-overview]]
|
|
|
|
|
=== Overview
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
It is important to be able to perform some integration testing without requiring
|
|
|
|
|
deployment to your application server or connecting to other enterprise infrastructure.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Doing so lets you test things such as:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
* The correct wiring of your Spring IoC container contexts.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* 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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
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
|
2018-09-18 22:42:09 +08:00
|
|
|
|
and might also be in the long `org.springframework.test` form, depending on where you get
|
2019-03-05 20:08:34 +08:00
|
|
|
|
it from (see the <<core.adoc#dependency-management, section on Dependency Management>>
|
|
|
|
|
for an explanation). This library includes the `org.springframework.test` package, which
|
2015-03-03 18:38:01 +08:00
|
|
|
|
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
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[integration-testing-goals]]
|
|
|
|
|
=== Goals of Integration Testing
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
Spring's integration testing support has the following primary goals:
|
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
* 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
|
2015-03-03 18:38:01 +08:00
|
|
|
|
developers in writing integration tests.
|
|
|
|
|
|
|
|
|
|
The next few sections describe each goal and provide links to implementation and
|
|
|
|
|
configuration details.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[testing-ctx-management]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Context Management and Caching
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The Spring TestContext Framework provides consistent loading of Spring
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Test classes typically declare either an array of resource locations for XML or Groovy
|
2019-09-25 19:29:17 +08:00
|
|
|
|
configuration metadata -- often in the classpath -- or an array of component classes that
|
2018-09-18 22:42:09 +08:00
|
|
|
|
is used to configure the application. These locations or classes are the same as or
|
2016-04-06 20:36:36 +08:00
|
|
|
|
similar to those specified in `web.xml` or other configuration files for production
|
|
|
|
|
deployments.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
By default, once loaded, the configured `ApplicationContext` is reused for each test.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Thus, the setup cost is incurred only once per test suite, and subsequent test execution
|
2018-09-18 21:43:14 +08:00
|
|
|
|
is much faster. In this context, the term "`test suite`" means all tests run in the same
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
See <<testcontext-ctx-management>> and <<testcontext-ctx-management-caching>> with the
|
|
|
|
|
TestContext framework.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[testing-fixture-di]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Dependency Injection of Test Fixtures
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
When the TestContext framework loads your application context, it can optionally
|
2018-08-30 23:29:17 +08:00
|
|
|
|
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
|
2015-03-03 18:38:01 +08:00
|
|
|
|
application context. A strong benefit here is that you can reuse application contexts
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
As an example, consider a scenario where we have a class (`HibernateTitleRepository`)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
that implements data access logic for a `Title` domain entity. We want to write
|
|
|
|
|
integration tests that test the following areas:
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* The Spring configuration: Basically, is everything related to the configuration of the
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`HibernateTitleRepository` bean correct and present?
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* The Hibernate mapping file configuration: Is everything mapped correctly and are the
|
2015-03-03 18:38:01 +08:00
|
|
|
|
correct lazy-loading settings in place?
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* The logic of the `HibernateTitleRepository`: Does the configured instance of this class
|
|
|
|
|
perform as anticipated?
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
See dependency injection of test fixtures with the
|
|
|
|
|
<<testcontext-fixture-di, TestContext framework>>.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[testing-tx]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Transaction Management
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
If you want a transaction to commit (unusual, but occasionally useful when you want a
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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
|
2015-09-11 01:35:17 +08:00
|
|
|
|
<<integration-testing-annotations, `@Commit`>> annotation.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
See transaction management with the <<testcontext-tx, TestContext framework>>.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[testing-support-classes]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Support Classes for Integration Testing
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
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,
|
2018-08-30 23:29:17 +08:00
|
|
|
|
which let you access:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
* The `ApplicationContext`, for performing explicit bean lookups or testing the state of
|
|
|
|
|
the context as a whole.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* 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
|
2019-03-05 20:08:34 +08:00
|
|
|
|
to avoid <<testcontext-tx-false-positives, false positives>>.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
In addition, you may want to create your own custom, application-wide superclass with
|
|
|
|
|
instance variables and methods specific to your project.
|
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
See support classes for the <<testcontext-support-classes, TestContext framework>>.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[integration-testing-support-jdbc]]
|
|
|
|
|
=== JDBC Testing Support
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The `org.springframework.test.jdbc` package contains `JdbcTestUtils`, which is a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
collection of JDBC-related utility functions intended to simplify standard database
|
2015-03-03 18:38:01 +08:00
|
|
|
|
testing scenarios. Specifically, `JdbcTestUtils` provides the following static utility
|
|
|
|
|
methods.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `countRowsInTable(..)`: Counts the number of rows in the given table.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* `countRowsInTableWhere(..)`: Counts the number of rows in the given table by using the
|
|
|
|
|
provided `WHERE` clause.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `deleteFromTables(..)`: Deletes all rows from the specified tables.
|
|
|
|
|
* `deleteFromTableWhere(..)`: Deletes rows from the given table by using the provided
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`WHERE` clause.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `dropTables(..)`: Drops the specified tables.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[TIP]
|
|
|
|
|
====
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<testcontext-support-classes-junit4, `AbstractTransactionalJUnit4SpringContextTests`>>
|
|
|
|
|
and <<testcontext-support-classes-testng, `AbstractTransactionalTestNGSpringContextTests`>>
|
2018-08-30 23:29:17 +08:00
|
|
|
|
provide convenience methods that delegate to the aforementioned methods in
|
|
|
|
|
`JdbcTestUtils`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
The `spring-jdbc` module provides support for configuring and launching an embedded
|
2019-03-05 20:08:34 +08:00
|
|
|
|
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
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Logic with an Embedded Database>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[integration-testing-annotations]]
|
|
|
|
|
=== Annotations
|
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
This section covers annotations that you can use when you test Spring applications.
|
|
|
|
|
It includes the following topics:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
* <<integration-testing-annotations-spring>>
|
|
|
|
|
* <<integration-testing-annotations-standard>>
|
|
|
|
|
* <<integration-testing-annotations-junit4>>
|
|
|
|
|
* <<integration-testing-annotations-junit-jupiter>>
|
|
|
|
|
* <<integration-testing-annotations-meta>>
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[integration-testing-annotations-spring]]
|
|
|
|
|
==== Spring Testing Annotations
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The Spring Framework provides the following set of Spring-specific annotations that you
|
|
|
|
|
can use in your unit and integration tests in conjunction with the TestContext framework.
|
2018-10-25 21:15:58 +08:00
|
|
|
|
See the corresponding javadoc for further information, including default attribute
|
2018-09-18 22:42:09 +08:00
|
|
|
|
values, attribute aliases, and other details.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
Spring's testing annotations include the following:
|
|
|
|
|
|
|
|
|
|
* <<spring-testing-annotation-bootstrapwith>>
|
|
|
|
|
* <<spring-testing-annotation-contextconfiguration>>
|
|
|
|
|
* <<spring-testing-annotation-webappconfiguration>>
|
|
|
|
|
* <<spring-testing-annotation-contexthierarchy>>
|
|
|
|
|
* <<spring-testing-annotation-activeprofiles>>
|
|
|
|
|
* <<spring-testing-annotation-testpropertysource>>
|
2020-04-01 23:56:58 +08:00
|
|
|
|
* <<spring-testing-annotation-dynamicpropertysource>>
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* <<spring-testing-annotation-dirtiescontext>>
|
|
|
|
|
* <<spring-testing-annotation-testexecutionlisteners>>
|
|
|
|
|
* <<spring-testing-annotation-commit>>
|
|
|
|
|
* <<spring-testing-annotation-rollback>>
|
|
|
|
|
* <<spring-testing-annotation-beforetransaction>>
|
|
|
|
|
* <<spring-testing-annotation-aftertransaction>>
|
|
|
|
|
* <<spring-testing-annotation-sql>>
|
|
|
|
|
* <<spring-testing-annotation-sqlconfig>>
|
2019-07-21 21:37:51 +08:00
|
|
|
|
* <<spring-testing-annotation-sqlmergemode>>
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* <<spring-testing-annotation-sqlgroup>>
|
|
|
|
|
|
|
|
|
|
[[spring-testing-annotation-bootstrapwith]]
|
|
|
|
|
===== `@BootstrapWith`
|
|
|
|
|
|
|
|
|
|
`@BootstrapWith` is a class-level annotation that you can use to configure how the Spring
|
2018-09-18 22:42:09 +08:00
|
|
|
|
TestContext Framework is bootstrapped. Specifically, you can use `@BootstrapWith` to
|
2019-03-05 20:08:34 +08:00
|
|
|
|
specify a custom `TestContextBootstrapper`. See the section on
|
|
|
|
|
<<testcontext-bootstrapping, bootstrapping the TestContext framework>> for further details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-contextconfiguration]]
|
|
|
|
|
===== `@ContextConfiguration`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@ContextConfiguration` defines class-level metadata that is used to determine how to
|
|
|
|
|
load and configure an `ApplicationContext` for integration tests. Specifically,
|
|
|
|
|
`@ContextConfiguration` declares the application context resource `locations` or the
|
2019-09-25 19:29:17 +08:00
|
|
|
|
component `classes` used to load the context.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Resource locations are typically XML configuration files or Groovy scripts located in the
|
2019-09-25 19:29:17 +08:00
|
|
|
|
classpath, while component classes are typically `@Configuration` classes. However,
|
|
|
|
|
resource locations can also refer to files and scripts in the file system, and component
|
|
|
|
|
classes can be `@Component` classes, `@Service` classes, and so on. See
|
|
|
|
|
<<testcontext-ctx-management-javaconfig-component-classes>> for further details.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
The following example shows a `@ContextConfiguration` annotation that refers to an XML
|
|
|
|
|
file:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration("/test-config.xml") // <1>
|
|
|
|
|
class XmlApplicationContextTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Referring to an XML file.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextConfiguration("/test-config.xml") // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class XmlApplicationContextTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:38:06 +08:00
|
|
|
|
<1> Referring to an XML file.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
The following example shows a `@ContextConfiguration` annotation that refers to a class:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration(classes = TestConfig.class) // <1>
|
|
|
|
|
class ConfigClassApplicationContextTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Referring to a class.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextConfiguration(classes = [TestConfig::class]) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ConfigClassApplicationContextTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Referring to a class.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-09-25 19:29:17 +08:00
|
|
|
|
As an alternative or in addition to declaring resource locations or component classes,
|
2018-08-30 23:29:17 +08:00
|
|
|
|
you can use `@ContextConfiguration` to declare `ApplicationContextInitializer` classes.
|
|
|
|
|
The following example shows such a case:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration(initializers = CustomContextIntializer.class) // <1>
|
|
|
|
|
class ContextInitializerTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextConfiguration(initializers = [CustomContextIntializer::class]) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ContextInitializerTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Declaring an initializer class.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
You can optionally use `@ContextConfiguration` to declare the `ContextLoader` strategy as
|
|
|
|
|
well. Note, however, that you typically do not need to explicitly configure the loader,
|
|
|
|
|
since the default loader supports `initializers` and either resource `locations` or
|
2019-09-25 19:29:17 +08:00
|
|
|
|
component `classes`.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
The following example uses both a location and a loader:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) // <1>
|
|
|
|
|
class CustomLoaderXmlApplicationContextTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Configuring both a location and a custom loader.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class CustomLoaderXmlApplicationContextTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Configuring both a location and a custom loader.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
NOTE: `@ContextConfiguration` provides support for inheriting resource locations or
|
2018-09-18 21:43:14 +08:00
|
|
|
|
configuration classes as well as context initializers that are declared by superclasses.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
See <<testcontext-ctx-management>> and the `@ContextConfiguration` javadocs for further
|
|
|
|
|
details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-webappconfiguration]]
|
|
|
|
|
===== `@WebAppConfiguration`
|
|
|
|
|
|
|
|
|
|
`@WebAppConfiguration` is a class-level annotation that you can use to declare that the
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`ApplicationContext` loaded for an integration test should be a `WebApplicationContext`.
|
|
|
|
|
The mere presence of `@WebAppConfiguration` on a test class ensures that a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`WebApplicationContext` is loaded for the test, using the default value of
|
|
|
|
|
`"file:src/main/webapp"` for the path to the root of the web application (that is, the
|
|
|
|
|
resource base path). The resource base path is used behind the scenes to create a
|
|
|
|
|
`MockServletContext`, which serves as the `ServletContext` for the test's
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`WebApplicationContext`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example shows how to use the `@WebAppConfiguration` annotation:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@WebAppConfiguration // <1>
|
|
|
|
|
class WebAppTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@WebAppConfiguration // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class WebAppTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> The `@WebAppConfiguration` annotation.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
To override the default, you can specify a different base resource path by using the
|
|
|
|
|
implicit `value` attribute. Both `classpath:` and `file:` resource prefixes are
|
|
|
|
|
supported. If no resource prefix is supplied, the path is assumed to be a file system
|
|
|
|
|
resource. The following example shows how to specify a classpath resource:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@WebAppConfiguration("classpath:test-web-resources") // <1>
|
|
|
|
|
class WebAppTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specifying a classpath resource.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@WebAppConfiguration("classpath:test-web-resources") // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class WebAppTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Specifying a classpath resource.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Note that `@WebAppConfiguration` must be used in conjunction with
|
|
|
|
|
`@ContextConfiguration`, either within a single test class or within a test class
|
2018-09-18 22:42:09 +08:00
|
|
|
|
hierarchy. See the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/test/context/web/WebAppConfiguration.html[`@WebAppConfiguration`]
|
|
|
|
|
javadoc for further details.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[spring-testing-annotation-contexthierarchy]]
|
|
|
|
|
===== `@ContextHierarchy`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@ContextHierarchy` is a class-level annotation that is used to define a hierarchy of
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ApplicationContext` instances for integration tests. `@ContextHierarchy` should be
|
|
|
|
|
declared with a list of one or more `@ContextConfiguration` instances, each of which
|
|
|
|
|
defines a level in the context hierarchy. The following examples demonstrate the use of
|
|
|
|
|
`@ContextHierarchy` within a single test class (`@ContextHierarchy` can also be used
|
|
|
|
|
within a test class hierarchy):
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextHierarchy({
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration("/parent-config.xml"),
|
|
|
|
|
@ContextConfiguration("/child-config.xml")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
})
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ContextHierarchyTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
ContextConfiguration("/parent-config.xml"),
|
|
|
|
|
ContextConfiguration("/child-config.xml"))
|
|
|
|
|
class ContextHierarchyTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@WebAppConfiguration
|
|
|
|
|
@ContextHierarchy({
|
|
|
|
|
@ContextConfiguration(classes = AppConfig.class),
|
|
|
|
|
@ContextConfiguration(classes = WebConfig.class)
|
|
|
|
|
})
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class WebIntegrationTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@WebAppConfiguration
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
ContextConfiguration(classes = [AppConfig::class]),
|
|
|
|
|
ContextConfiguration(classes = [WebConfig::class]))
|
|
|
|
|
class WebIntegrationTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
If you need to merge or override the configuration for a given level of the context
|
2018-09-18 22:42:09 +08:00
|
|
|
|
hierarchy within a test class hierarchy, you must explicitly name that level by supplying
|
|
|
|
|
the same value to the `name` attribute in `@ContextConfiguration` at each corresponding
|
|
|
|
|
level in the class hierarchy. See <<testcontext-ctx-management-ctx-hierarchies>> and the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/test/context/ContextHierarchy.html[`@ContextHierarchy`] javadoc
|
2015-03-03 18:38:01 +08:00
|
|
|
|
for further examples.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-activeprofiles]]
|
|
|
|
|
===== `@ActiveProfiles`
|
|
|
|
|
|
|
|
|
|
`@ActiveProfiles` is a class-level annotation that is used to declare which bean
|
|
|
|
|
definition profiles should be active when loading an `ApplicationContext` for an
|
2016-06-09 23:03:44 +08:00
|
|
|
|
integration test.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example indicates that the `dev` profile should be active:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@ActiveProfiles("dev") // <1>
|
|
|
|
|
class DeveloperTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Indicate that the `dev` profile should be active.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ActiveProfiles("dev") // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class DeveloperTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Indicate that the `dev` profile should be active.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
The following example indicates that both the `dev` and the `integration` profiles should
|
|
|
|
|
be active:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ActiveProfiles({"dev", "integration"}) // <1>
|
|
|
|
|
class DeveloperIntegrationTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Indicate that the `dev` and `integration` profiles should be active.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@ActiveProfiles(["dev", "integration"]) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class DeveloperIntegrationTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Indicate that the `dev` and `integration` profiles should be active.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
NOTE: `@ActiveProfiles` provides support for inheriting active bean definition profiles
|
2018-09-18 22:42:09 +08:00
|
|
|
|
declared by superclasses by default. You can also resolve active bean definition profiles
|
|
|
|
|
programmatically by implementing a custom
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<testcontext-ctx-management-env-profiles-ActiveProfilesResolver, `ActiveProfilesResolver`>>
|
2018-08-30 23:29:17 +08:00
|
|
|
|
and registering it by using the `resolver` attribute of `@ActiveProfiles`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
See <<testcontext-ctx-management-env-profiles>> and the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/test/context/ActiveProfiles.html[`@ActiveProfiles`] javadoc for
|
2018-09-18 22:42:09 +08:00
|
|
|
|
examples and further details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-testpropertysource]]
|
|
|
|
|
===== `@TestPropertySource`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`@TestPropertySource` is a class-level annotation that you can use to configure the
|
|
|
|
|
locations of properties files and inlined properties to be added to the set of
|
|
|
|
|
`PropertySources` in the `Environment` for an `ApplicationContext` loaded for an
|
|
|
|
|
integration test.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example demonstrates how to declare a properties file from the classpath:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@TestPropertySource("/test.properties") // <1>
|
|
|
|
|
class MyIntegrationTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Get properties from `test.properties` in the root of the classpath.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestPropertySource("/test.properties") // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyIntegrationTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Get properties from `test.properties` in the root of the classpath.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example demonstrates how to declare inlined properties:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) // <1>
|
|
|
|
|
class MyIntegrationTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Declare `timezone` and `port` properties.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyIntegrationTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Declare `timezone` and `port` properties.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-07-28 05:02:43 +08:00
|
|
|
|
See <<testcontext-ctx-management-property-sources>> for examples and further details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2020-04-01 23:56:58 +08:00
|
|
|
|
[[spring-testing-annotation-dynamicpropertysource]]
|
|
|
|
|
===== `@DynamicPropertySource`
|
|
|
|
|
|
|
|
|
|
`@DynamicPropertySource` is a method-level annotation that you can use to register
|
|
|
|
|
_dynamic_ properties to be added to the set of `PropertySources` in the `Environment` for
|
|
|
|
|
an `ApplicationContext` loaded for an integration test. Dynamic properties are useful
|
|
|
|
|
when you do not know the value of the properties upfront – for example, if the properties
|
|
|
|
|
are managed by an external resource such as for a container managed by the
|
|
|
|
|
https://www.testcontainers.org/[Testcontainers] project.
|
|
|
|
|
|
|
|
|
|
The following example demonstrates how to register a dynamic property:
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
class MyIntegrationTests {
|
|
|
|
|
|
|
|
|
|
static MyExternalServer server = // ...
|
|
|
|
|
|
|
|
|
|
@DynamicPropertySource // <1>
|
|
|
|
|
static void dynamicProperties(DynamicPropertyRegistry registry) { // <2>
|
|
|
|
|
registry.add("server.port", server::getPort); // <3>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tests ...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Annotate a `static` method with `@DynamicPropertySource`.
|
|
|
|
|
<2> Accept a `DynamicPropertyRegistry` as an argument.
|
|
|
|
|
<3> Register a dynamic `server.port` property to be retrieved lazily from the server.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
class MyIntegrationTests {
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
|
|
@JvmStatic
|
|
|
|
|
val server: MyExternalServer = // ...
|
|
|
|
|
|
|
|
|
|
@DynamicPropertySource // <1>
|
|
|
|
|
@JvmStatic
|
|
|
|
|
fun dynamicProperties(registry: DynamicPropertyRegistry) { // <2>
|
|
|
|
|
registry.add("server.port", server::getPort) // <3>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tests ...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Annotate a `static` method with `@DynamicPropertySource`.
|
|
|
|
|
<2> Accept a `DynamicPropertyRegistry` as an argument.
|
|
|
|
|
<3> Register a dynamic `server.port` property to be retrieved lazily from the server.
|
|
|
|
|
|
|
|
|
|
See <<testcontext-ctx-management-dynamic-property-sources>> for further details.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-dirtiescontext]]
|
|
|
|
|
===== `@DirtiesContext`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@DirtiesContext` indicates that the underlying Spring `ApplicationContext` has been
|
2018-09-18 22:42:09 +08:00
|
|
|
|
dirtied during the execution of a test (that is, the test modified or corrupted it in
|
|
|
|
|
some manner -- for example, by changing the state of a singleton bean) and should be
|
|
|
|
|
closed. When an application context is marked as dirty, it is removed from the testing
|
|
|
|
|
framework's cache and closed. As a consequence, the underlying Spring container is
|
|
|
|
|
rebuilt for any subsequent test that requires a context with the same configuration
|
|
|
|
|
metadata.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can use `@DirtiesContext` as both a class-level and a method-level annotation within
|
2015-03-23 01:42:41 +08:00
|
|
|
|
the same class or class hierarchy. In such scenarios, the `ApplicationContext` is marked
|
2018-09-18 22:42:09 +08:00
|
|
|
|
as dirty before or after any such annotated method as well as before or after the current
|
|
|
|
|
test class, depending on the configured `methodMode` and `classMode`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
The following examples explain when the context would be dirtied for various
|
|
|
|
|
configuration scenarios:
|
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
* Before the current test class, when declared on a class with class mode set to
|
2015-03-23 01:42:41 +08:00
|
|
|
|
`BEFORE_CLASS`.
|
|
|
|
|
+
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-23 01:42:41 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@DirtiesContext(classMode = BEFORE_CLASS) // <1>
|
|
|
|
|
class FreshContextTests {
|
|
|
|
|
// some tests that require a new Spring container
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Dirty the context before the current test class.
|
|
|
|
|
+
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@DirtiesContext(classMode = BEFORE_CLASS) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class FreshContextTests {
|
2015-03-23 01:42:41 +08:00
|
|
|
|
// some tests that require a new Spring container
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Dirty the context before the current test class.
|
2015-03-23 01:42:41 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
* After the current test class, when declared on a class with class mode set to
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`AFTER_CLASS` (i.e., the default class mode).
|
|
|
|
|
+
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@DirtiesContext // <1>
|
|
|
|
|
class ContextDirtyingTests {
|
|
|
|
|
// some tests that result in the Spring container being dirtied
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Dirty the context after the current test class.
|
|
|
|
|
+
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@DirtiesContext // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ContextDirtyingTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// some tests that result in the Spring container being dirtied
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Dirty the context after the current test class.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
* Before each test method in the current test class, when declared on a class with class
|
2015-03-23 01:42:41 +08:00
|
|
|
|
mode set to `BEFORE_EACH_TEST_METHOD.`
|
|
|
|
|
+
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-23 01:42:41 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // <1>
|
|
|
|
|
class FreshContextTests {
|
|
|
|
|
// some tests that require a new Spring container
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Dirty the context before each test method.
|
|
|
|
|
+
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class FreshContextTests {
|
2015-03-23 01:42:41 +08:00
|
|
|
|
// some tests that require a new Spring container
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Dirty the context before each test method.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-23 01:42:41 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
* After each test method in the current test class, when declared on a class with class
|
2015-03-03 18:38:01 +08:00
|
|
|
|
mode set to `AFTER_EACH_TEST_METHOD.`
|
|
|
|
|
+
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) // <1>
|
|
|
|
|
class ContextDirtyingTests {
|
|
|
|
|
// some tests that result in the Spring container being dirtied
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Dirty the context after each test method.
|
|
|
|
|
+
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ContextDirtyingTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// some tests that result in the Spring container being dirtied
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Dirty the context after each test method.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
* Before the current test, when declared on a method with the method mode set to
|
2015-03-23 01:42:41 +08:00
|
|
|
|
`BEFORE_METHOD`.
|
|
|
|
|
+
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-23 01:42:41 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@DirtiesContext(methodMode = BEFORE_METHOD) // <1>
|
2015-03-23 01:42:41 +08:00
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void testProcessWhichRequiresFreshAppCtx() {
|
2015-03-23 01:42:41 +08:00
|
|
|
|
// some logic that requires a new Spring container
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Dirty the context before the current test method.
|
2019-08-30 08:35:53 +08:00
|
|
|
|
+
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@DirtiesContext(methodMode = BEFORE_METHOD) // <1>
|
|
|
|
|
@Test
|
|
|
|
|
fun testProcessWhichRequiresFreshAppCtx() {
|
|
|
|
|
// some logic that requires a new Spring container
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Dirty the context before the current test method.
|
2015-03-23 01:42:41 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
* After the current test, when declared on a method with the method mode set to
|
2015-03-23 01:42:41 +08:00
|
|
|
|
`AFTER_METHOD` (i.e., the default method mode).
|
2015-03-03 18:38:01 +08:00
|
|
|
|
+
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@DirtiesContext // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void testProcessWhichDirtiesAppCtx() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// some logic that results in the Spring container being dirtied
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Dirty the context after the current test method.
|
2019-08-30 08:35:53 +08:00
|
|
|
|
+
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@DirtiesContext // <1>
|
|
|
|
|
@Test
|
|
|
|
|
fun testProcessWhichDirtiesAppCtx() {
|
|
|
|
|
// some logic that results in the Spring container being dirtied
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Dirty the context after the current test method.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
If you use `@DirtiesContext` in a test whose context is configured as part of a context
|
|
|
|
|
hierarchy with `@ContextHierarchy`, you can use the `hierarchyMode` flag to control how
|
2018-09-18 22:42:09 +08:00
|
|
|
|
the context cache is cleared. By default, an exhaustive algorithm is used to clear the
|
|
|
|
|
context cache, including not only the current level but also all other context
|
2018-08-30 23:29:17 +08:00
|
|
|
|
hierarchies that share an ancestor context common to the current test. All
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ApplicationContext` instances that reside in a sub-hierarchy of the common ancestor
|
|
|
|
|
context are removed from the context cache and closed. If the exhaustive algorithm is
|
2018-08-30 23:29:17 +08:00
|
|
|
|
overkill for a particular use case, you can specify the simpler current level algorithm,
|
|
|
|
|
as the following example shows.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextHierarchy({
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration("/parent-config.xml"),
|
|
|
|
|
@ContextConfiguration("/child-config.xml")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
})
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class BaseTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ExtendedTests extends BaseTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void test() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// some logic that results in the child context being dirtied
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Use the current-level algorithm.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
ContextConfiguration("/parent-config.xml"),
|
|
|
|
|
ContextConfiguration("/child-config.xml"))
|
|
|
|
|
open class BaseTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ExtendedTests : BaseTests() {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) // <1>
|
|
|
|
|
fun test() {
|
|
|
|
|
// some logic that results in the child context being dirtied
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Use the current-level algorithm.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
For further details regarding the `EXHAUSTIVE` and `CURRENT_LEVEL` algorithms, see the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/test/annotation/DirtiesContext.HierarchyMode.html[`DirtiesContext.HierarchyMode`]
|
|
|
|
|
javadoc.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[spring-testing-annotation-testexecutionlisteners]]
|
|
|
|
|
===== `@TestExecutionListeners`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@TestExecutionListeners` defines class-level metadata for configuring the
|
|
|
|
|
`TestExecutionListener` implementations that should be registered with the
|
|
|
|
|
`TestContextManager`. Typically, `@TestExecutionListeners` is used in conjunction with
|
|
|
|
|
`@ContextConfiguration`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
The following example shows how to register two `TestExecutionListener` implementations:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) // <1>
|
|
|
|
|
class CustomTestExecutionListenerTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Register two `TestExecutionListener` implementations.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class CustomTestExecutionListenerTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Register two `TestExecutionListener` implementations.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
By default, `@TestExecutionListeners` supports inherited listeners. See the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/test/context/TestExecutionListeners.html[javadoc]
|
|
|
|
|
for an example and further details.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[spring-testing-annotation-commit]]
|
|
|
|
|
===== `@Commit`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@Commit` indicates that the transaction for a transactional test method should be
|
2018-08-30 23:29:17 +08:00
|
|
|
|
committed after the test method has completed. You can use `@Commit` as a direct
|
2018-09-18 22:42:09 +08:00
|
|
|
|
replacement for `@Rollback(false)` to more explicitly convey the intent of the code.
|
|
|
|
|
Analogous to `@Rollback`, `@Commit` can also be declared as a class-level or method-level
|
|
|
|
|
annotation.
|
2015-07-26 03:09:32 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example shows how to use the `@Commit` annotation:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-07-26 03:09:32 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Commit // <1>
|
2015-07-26 03:09:32 +08:00
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void testProcessWithoutRollback() {
|
2015-07-26 03:09:32 +08:00
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Commit the result of the test to the database.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Commit // <1>
|
|
|
|
|
@Test
|
|
|
|
|
fun testProcessWithoutRollback() {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Commit the result of the test to the database.
|
|
|
|
|
|
2015-07-26 03:09:32 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-rollback]]
|
|
|
|
|
===== `@Rollback`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@Rollback` indicates whether the transaction for a transactional test method should be
|
2018-08-30 23:29:17 +08:00
|
|
|
|
rolled back after the test method has completed. If `true`, the transaction is rolled
|
2018-09-18 22:42:09 +08:00
|
|
|
|
back. Otherwise, the transaction is committed (see also
|
|
|
|
|
<<spring-testing-annotation-commit>>). Rollback for integration tests in the Spring
|
|
|
|
|
TestContext Framework defaults to `true` even if `@Rollback` is not explicitly declared.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2015-07-26 00:22:26 +08:00
|
|
|
|
When declared as a class-level annotation, `@Rollback` defines the default rollback
|
|
|
|
|
semantics for all test methods within the test class hierarchy. When declared as a
|
|
|
|
|
method-level annotation, `@Rollback` defines rollback semantics for the specific test
|
2015-10-16 22:08:42 +08:00
|
|
|
|
method, potentially overriding class-level `@Rollback` or `@Commit` semantics.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example causes a test method's result to not be rolled back (that is, the
|
|
|
|
|
result is committed to the database):
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Rollback(false) // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void testProcessWithoutRollback() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Do not roll back the result.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Rollback(false) // <1>
|
|
|
|
|
@Test
|
|
|
|
|
fun testProcessWithoutRollback() {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Do not roll back the result.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[spring-testing-annotation-beforetransaction]]
|
|
|
|
|
===== `@BeforeTransaction`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`@BeforeTransaction` indicates that the annotated `void` method should be run before a
|
|
|
|
|
transaction is started, for test methods that have been configured to run within a
|
2019-09-26 21:02:30 +08:00
|
|
|
|
transaction by using Spring's `@Transactional` annotation. `@BeforeTransaction` methods
|
|
|
|
|
are not required to be `public` and may be declared on Java 8-based interface default
|
|
|
|
|
methods.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example shows how to use the `@BeforeTransaction` annotation:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@BeforeTransaction // <1>
|
2016-02-29 06:36:21 +08:00
|
|
|
|
void beforeTransaction() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// logic to be executed before a transaction is started
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Run this method before a transaction.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@BeforeTransaction // <1>
|
|
|
|
|
fun beforeTransaction() {
|
|
|
|
|
// logic to be executed before a transaction is started
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Run this method before a transaction.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-aftertransaction]]
|
|
|
|
|
===== `@AfterTransaction`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`@AfterTransaction` indicates that the annotated `void` method should be run after a
|
|
|
|
|
transaction is ended, for test methods that have been configured to run within a
|
2019-09-26 21:02:30 +08:00
|
|
|
|
transaction by using Spring's `@Transactional` annotation. `@AfterTransaction` methods
|
|
|
|
|
are not required to be `public` and may be declared on Java 8-based interface default
|
|
|
|
|
methods.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@AfterTransaction // <1>
|
2016-02-29 06:36:21 +08:00
|
|
|
|
void afterTransaction() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// logic to be executed after a transaction has ended
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Run this method after a transaction.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@AfterTransaction // <1>
|
|
|
|
|
fun afterTransaction() {
|
|
|
|
|
// logic to be executed after a transaction has ended
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Run this method after a transaction.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[spring-testing-annotation-sql]]
|
|
|
|
|
===== `@Sql`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`@Sql` is used to annotate a test class or test method to configure SQL scripts to be run
|
|
|
|
|
against a given database during integration tests. The following example shows how to use
|
|
|
|
|
it:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Test
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Sql({"/test-schema.sql", "/test-user-data.sql"}) // <1>
|
2019-08-30 23:12:23 +08:00
|
|
|
|
void userTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute code that relies on the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Run two scripts for this test.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
@Sql("/test-schema.sql", "/test-user-data.sql") // <1>
|
|
|
|
|
fun userTest() {
|
|
|
|
|
// execute code that relies on the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Run two scripts for this test.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
See <<testcontext-executing-sql-declaratively>> for further details.
|
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-sqlconfig]]
|
|
|
|
|
===== `@SqlConfig`
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`@SqlConfig` defines metadata that is used to determine how to parse and run SQL scripts
|
|
|
|
|
configured with the `@Sql` annotation. The following example shows how to use it:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
@Sql(
|
|
|
|
|
scripts = "/test-user-data.sql",
|
2019-08-30 08:35:53 +08:00
|
|
|
|
config = @SqlConfig(commentPrefix = "`", separator = "@@") // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
)
|
2019-08-30 23:12:23 +08:00
|
|
|
|
void userTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute code that relies on the test data
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Set the comment prefix and the separator in SQL scripts.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
@Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) // <1>
|
|
|
|
|
fun userTest() {
|
|
|
|
|
// execute code that relies on the test data
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Set the comment prefix and the separator in SQL scripts.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
[[spring-testing-annotation-sqlmergemode]]
|
|
|
|
|
===== `@SqlMergeMode`
|
|
|
|
|
|
|
|
|
|
`@SqlMergeMode` is used to annotate a test class or test method to configure whether
|
|
|
|
|
method-level `@Sql` declarations are merged with class-level `@Sql` declarations. If
|
|
|
|
|
`@SqlMergeMode` is not declared on a test class or test method, the `OVERRIDE` merge mode
|
|
|
|
|
will be used by default. With the `OVERRIDE` mode, method-level `@Sql` declarations will
|
|
|
|
|
effectively override class-level `@Sql` declarations.
|
|
|
|
|
|
|
|
|
|
Note that a method-level `@SqlMergeMode` declaration overrides a class-level declaration.
|
|
|
|
|
|
|
|
|
|
The following example shows how to use `@SqlMergeMode` at the class level.
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2019-07-21 21:37:51 +08:00
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig.class)
|
|
|
|
|
@Sql("/test-schema.sql")
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SqlMergeMode(MERGE) // <1>
|
2019-07-21 21:37:51 +08:00
|
|
|
|
class UserTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Sql("/user-test-data-001.sql")
|
2019-08-30 23:19:46 +08:00
|
|
|
|
void standardUserProfile() {
|
2019-07-21 21:37:51 +08:00
|
|
|
|
// execute code that relies on test data set 001
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class.
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
|
|
|
@Sql("/test-schema.sql")
|
|
|
|
|
@SqlMergeMode(MERGE) // <1>
|
|
|
|
|
class UserTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Sql("/user-test-data-001.sql")
|
|
|
|
|
fun standardUserProfile() {
|
|
|
|
|
// execute code that relies on test data set 001
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class.
|
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
The following example shows how to use `@SqlMergeMode` at the method level.
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2019-07-21 21:37:51 +08:00
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig.class)
|
|
|
|
|
@Sql("/test-schema.sql")
|
|
|
|
|
class UserTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Sql("/user-test-data-001.sql")
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SqlMergeMode(MERGE) // <1>
|
2019-08-30 23:19:46 +08:00
|
|
|
|
void standardUserProfile() {
|
2019-07-21 21:37:51 +08:00
|
|
|
|
// execute code that relies on test data set 001
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Set the `@Sql` merge mode to `MERGE` for a specific test method.
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
|
|
|
@Sql("/test-schema.sql")
|
|
|
|
|
class UserTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Sql("/user-test-data-001.sql")
|
|
|
|
|
@SqlMergeMode(MERGE) // <1>
|
|
|
|
|
fun standardUserProfile() {
|
|
|
|
|
// execute code that relies on test data set 001
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Set the `@Sql` merge mode to `MERGE` for a specific test method.
|
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[spring-testing-annotation-sqlgroup]]
|
|
|
|
|
===== `@SqlGroup`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`@SqlGroup` is a container annotation that aggregates several `@Sql` annotations. You can
|
|
|
|
|
use `@SqlGroup` natively to declare several nested `@Sql` annotations, or you can use it
|
|
|
|
|
in conjunction with Java 8's support for repeatable annotations, where `@Sql` can be
|
|
|
|
|
declared several times on the same class or method, implicitly generating this container
|
|
|
|
|
annotation. The following example shows how to declare an SQL group:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Test
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SqlGroup({ // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@Sql("/test-user-data.sql")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
)}
|
2019-08-30 23:12:23 +08:00
|
|
|
|
void userTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute code that uses the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Declare a group of SQL scripts.
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
@SqlGroup( // <1>
|
|
|
|
|
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
|
|
|
|
|
Sql("/test-user-data.sql"))
|
|
|
|
|
fun userTest() {
|
|
|
|
|
// execute code that uses the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Declare a group of SQL scripts.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[integration-testing-annotations-standard]]
|
|
|
|
|
==== Standard Annotation Support
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following annotations are supported with standard semantics for all configurations of
|
|
|
|
|
the Spring TestContext Framework. Note that these annotations are not specific to tests
|
|
|
|
|
and can be used anywhere in the Spring Framework.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
* `@Autowired`
|
|
|
|
|
* `@Qualifier`
|
2019-06-18 19:27:09 +08:00
|
|
|
|
* `@Value`
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `@Resource` (javax.annotation) if JSR-250 is present
|
|
|
|
|
* `@ManagedBean` (javax.annotation) if JSR-250 is present
|
|
|
|
|
* `@Inject` (javax.inject) if JSR-330 is present
|
|
|
|
|
* `@Named` (javax.inject) if JSR-330 is present
|
|
|
|
|
* `@PersistenceContext` (javax.persistence) if JPA is present
|
|
|
|
|
* `@PersistenceUnit` (javax.persistence) if JPA is present
|
2015-03-03 18:38:01 +08:00
|
|
|
|
* `@Required`
|
2019-06-18 19:27:09 +08:00
|
|
|
|
* `@Transactional` (org.springframework.transaction.annotation)
|
|
|
|
|
_with <<testcontext-tx-attribute-support, limited attribute support>>_
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.JSR-250 Lifecycle Annotations
|
|
|
|
|
[NOTE]
|
2015-03-06 23:57:32 +08:00
|
|
|
|
====
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In the Spring TestContext Framework, you can use `@PostConstruct` and `@PreDestroy` with
|
|
|
|
|
standard semantics on any application components configured in the `ApplicationContext`.
|
|
|
|
|
However, these lifecycle annotations have limited usage within an actual test class.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If a method within a test class is annotated with `@PostConstruct`, that method runs
|
|
|
|
|
before any before methods of the underlying test framework (for example, methods
|
|
|
|
|
annotated with JUnit Jupiter's `@BeforeEach`), and that applies for every test method in
|
|
|
|
|
the test class. On the other hand, if a method within a test class is annotated with
|
|
|
|
|
`@PreDestroy`, that method never runs. Therefore, within a test class, we recommend that
|
|
|
|
|
you use test lifecycle callbacks from the underlying test framework instead of
|
|
|
|
|
`@PostConstruct` and `@PreDestroy`.
|
2015-03-06 23:57:32 +08:00
|
|
|
|
====
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
2017-09-27 22:25:16 +08:00
|
|
|
|
[[integration-testing-annotations-junit4]]
|
2016-05-04 00:45:47 +08:00
|
|
|
|
==== Spring JUnit 4 Testing Annotations
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following annotations are supported only when used in conjunction with the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<testcontext-junit4-runner, SpringRunner>>, <<testcontext-junit4-rules, Spring's JUnit 4
|
|
|
|
|
rules>>, or <<testcontext-support-classes-junit4, Spring's JUnit 4 support classes>>:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* <<integration-testing-annotations-junit4-ifprofilevalue>>
|
|
|
|
|
* <<integration-testing-annotations-junit4-profilevaluesourceconfiguration>>
|
|
|
|
|
* <<integration-testing-annotations-junit4-timed>>
|
|
|
|
|
* <<integration-testing-annotations-junit4-repeat>>
|
|
|
|
|
|
|
|
|
|
[[integration-testing-annotations-junit4-ifprofilevalue]]
|
|
|
|
|
===== `@IfProfileValue`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@IfProfileValue` indicates that the annotated test is enabled for a specific testing
|
|
|
|
|
environment. If the configured `ProfileValueSource` returns a matching `value` for the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
provided `name`, the test is enabled. Otherwise, the test is disabled and, effectively,
|
|
|
|
|
ignored.
|
2015-10-16 06:48:04 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can apply `@IfProfileValue` at the class level, the method level, or both.
|
2015-07-10 07:38:54 +08:00
|
|
|
|
Class-level usage of `@IfProfileValue` takes precedence over method-level usage for any
|
|
|
|
|
methods within that class or its subclasses. Specifically, a test is enabled if it is
|
2018-09-18 22:42:09 +08:00
|
|
|
|
enabled both at the class level and at the method level. The absence of `@IfProfileValue`
|
|
|
|
|
means the test is implicitly enabled. This is analogous to the semantics of JUnit 4's
|
|
|
|
|
`@Ignore` annotation, except that the presence of `@Ignore` always disables a test.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example shows a test that has an `@IfProfileValue` annotation:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Test
|
|
|
|
|
public void testProcessWhichRunsOnlyOnOracleJvm() {
|
|
|
|
|
// some logic that should run only on Java VMs from Oracle Corporation
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Run this test only when the Java vendor is "Oracle Corporation".
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1>
|
|
|
|
|
@Test
|
|
|
|
|
fun testProcessWhichRunsOnlyOnOracleJvm() {
|
|
|
|
|
// some logic that should run only on Java VMs from Oracle Corporation
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Run this test only when the Java vendor is "Oracle Corporation".
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Alternatively, you can configure `@IfProfileValue` with a list of `values` (with `OR`
|
|
|
|
|
semantics) to achieve TestNG-like support for test groups in a JUnit 4 environment.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
Consider the following example:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Test
|
|
|
|
|
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
|
|
|
|
|
// some logic that should run only for unit and integration test groups
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Run this test for unit tests and integration tests.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) // <1>
|
|
|
|
|
@Test
|
|
|
|
|
fun testProcessWhichRunsForUnitOrIntegrationTestGroups() {
|
|
|
|
|
// some logic that should run only for unit and integration test groups
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Run this test for unit tests and integration tests.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[integration-testing-annotations-junit4-profilevaluesourceconfiguration]]
|
|
|
|
|
===== `@ProfileValueSourceConfiguration`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@ProfileValueSourceConfiguration` is a class-level annotation that specifies what type
|
2018-08-30 23:29:17 +08:00
|
|
|
|
of `ProfileValueSource` to use when retrieving profile values configured through the
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@IfProfileValue` annotation. If `@ProfileValueSourceConfiguration` is not declared for a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
test, `SystemProfileValueSource` is used by default. The following example shows how to
|
|
|
|
|
use `@ProfileValueSourceConfiguration`:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
public class CustomProfileValueSourceTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Use a custom profile value source.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ProfileValueSourceConfiguration(CustomProfileValueSource::class) // <1>
|
|
|
|
|
class CustomProfileValueSourceTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Use a custom profile value source.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[integration-testing-annotations-junit4-timed]]
|
|
|
|
|
===== `@Timed`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-09 23:03:44 +08:00
|
|
|
|
`@Timed` indicates that the annotated test method must finish execution in a specified
|
|
|
|
|
time period (in milliseconds). If the text execution time exceeds the specified time
|
|
|
|
|
period, the test fails.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The time period includes running the test method itself, any repetitions of the test (see
|
|
|
|
|
`@Repeat`), as well as any setting up or tearing down of the test fixture. The following
|
|
|
|
|
example shows how to use it:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Timed(millis = 1000) // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
public void testProcessWithOneSecondTimeout() {
|
|
|
|
|
// some logic that should not take longer than 1 second to execute
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Set the time period for the test to one second.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Timed(millis = 1000) // <1>
|
|
|
|
|
fun testProcessWithOneSecondTimeout() {
|
|
|
|
|
// some logic that should not take longer than 1 second to execute
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Set the time period for the test to one second.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2016-05-04 00:45:47 +08:00
|
|
|
|
Spring's `@Timed` annotation has different semantics than JUnit 4's `@Test(timeout=...)`
|
|
|
|
|
support. Specifically, due to the manner in which JUnit 4 handles test execution timeouts
|
2015-03-03 18:38:01 +08:00
|
|
|
|
(that is, by executing the test method in a separate `Thread`), `@Test(timeout=...)`
|
|
|
|
|
preemptively fails the test if the test takes too long. Spring's `@Timed`, on the other
|
|
|
|
|
hand, does not preemptively fail the test but rather waits for the test to complete
|
|
|
|
|
before failing.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[integration-testing-annotations-junit4-repeat]]
|
|
|
|
|
===== `@Repeat`
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`@Repeat` indicates that the annotated test method must be run repeatedly. The number of
|
|
|
|
|
times that the test method is to be executed is specified in the annotation.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
The scope of execution to be repeated includes execution of the test method itself as
|
2018-09-18 22:42:09 +08:00
|
|
|
|
well as any setting up or tearing down of the test fixture. The following example shows
|
|
|
|
|
how to use the `@Repeat` annotation:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Repeat(10) // <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Test
|
|
|
|
|
public void testProcessRepeatedly() {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Repeat this test ten times.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Repeat(10) // <1>
|
|
|
|
|
@Test
|
|
|
|
|
fun testProcessRepeatedly() {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Repeat this test ten times.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-09-27 22:25:16 +08:00
|
|
|
|
[[integration-testing-annotations-junit-jupiter]]
|
|
|
|
|
==== Spring JUnit Jupiter Testing Annotations
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following annotations are supported only when used in conjunction with the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<testcontext-junit-jupiter-extension, `SpringExtension`>> and JUnit Jupiter
|
|
|
|
|
(that is, the programming model in JUnit 5):
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
* <<integration-testing-annotations-junit-jupiter-springjunitconfig>>
|
|
|
|
|
* <<integration-testing-annotations-junit-jupiter-springjunitwebconfig>>
|
2019-05-09 22:07:21 +08:00
|
|
|
|
* <<integration-testing-annotations-testconstructor>>
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* <<integration-testing-annotations-junit-jupiter-enabledif>>
|
2018-09-21 20:45:25 +08:00
|
|
|
|
* <<integration-testing-annotations-junit-jupiter-disabledif>>
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
|
|
|
|
|
===== `@SpringJUnitConfig`
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`@SpringJUnitConfig` is a composed annotation that combines
|
2017-09-27 22:25:16 +08:00
|
|
|
|
`@ExtendWith(SpringExtension.class)` from JUnit Jupiter with `@ContextConfiguration` from
|
|
|
|
|
the Spring TestContext Framework. It can be used at the class level as a drop-in
|
|
|
|
|
replacement for `@ContextConfiguration`. With regard to configuration options, the only
|
2019-09-25 19:29:17 +08:00
|
|
|
|
difference between `@ContextConfiguration` and `@SpringJUnitConfig` is that component
|
2018-08-30 23:29:17 +08:00
|
|
|
|
classes may be declared with the `value` attribute in `@SpringJUnitConfig`.
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example shows how to use the `@SpringJUnitConfig` annotation to specify a
|
|
|
|
|
configuration class:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig.class) // <1>
|
|
|
|
|
class ConfigurationClassJUnitJupiterSpringTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specify the configuration class.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitConfig(TestConfig::class) // <1>
|
2017-09-27 22:25:16 +08:00
|
|
|
|
class ConfigurationClassJUnitJupiterSpringTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Specify the configuration class.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following example shows how to use the `@SpringJUnitConfig` annotation to specify the
|
|
|
|
|
location of a configuration file:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(locations = "/test-config.xml") // <1>
|
|
|
|
|
class XmlJUnitJupiterSpringTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specify the location of a configuration file.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitConfig(locations = ["/test-config.xml"]) // <1>
|
2017-09-27 22:25:16 +08:00
|
|
|
|
class XmlJUnitJupiterSpringTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Specify the location of a configuration file.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
See <<testcontext-ctx-management>> as well as the javadoc for
|
|
|
|
|
{api-spring-framework}/test/context/junit/jupiter/SpringJUnitConfig.html[`@SpringJUnitConfig`]
|
|
|
|
|
and `@ContextConfiguration` for further details.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
[[integration-testing-annotations-junit-jupiter-springjunitwebconfig]]
|
|
|
|
|
===== `@SpringJUnitWebConfig`
|
|
|
|
|
|
|
|
|
|
`@SpringJUnitWebConfig` is a composed annotation that combines
|
2017-09-27 22:25:16 +08:00
|
|
|
|
`@ExtendWith(SpringExtension.class)` from JUnit Jupiter with `@ContextConfiguration` and
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`@WebAppConfiguration` from the Spring TestContext Framework. You can use it at the class
|
2017-09-27 22:25:16 +08:00
|
|
|
|
level as a drop-in replacement for `@ContextConfiguration` and `@WebAppConfiguration`.
|
|
|
|
|
With regard to configuration options, the only difference between `@ContextConfiguration`
|
2019-09-25 19:29:17 +08:00
|
|
|
|
and `@SpringJUnitWebConfig` is that you can declare component classes by using the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`value` attribute in `@SpringJUnitWebConfig`. In addition, you can override the `value`
|
|
|
|
|
attribute from `@WebAppConfiguration` only by using the `resourcePath` attribute in
|
2017-09-27 22:25:16 +08:00
|
|
|
|
`@SpringJUnitWebConfig`.
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify
|
|
|
|
|
a configuration class:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitWebConfig(TestConfig.class) // <1>
|
|
|
|
|
class ConfigurationClassJUnitJupiterSpringWebTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specify the configuration class.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitWebConfig(TestConfig::class) // <1>
|
2017-09-27 22:25:16 +08:00
|
|
|
|
class ConfigurationClassJUnitJupiterSpringWebTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Specify the configuration class.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2020-03-02 23:25:28 +08:00
|
|
|
|
The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify the
|
|
|
|
|
location of a configuration file:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitWebConfig(locations = "/test-config.xml") // <1>
|
|
|
|
|
class XmlJUnitJupiterSpringWebTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specify the location of a configuration file.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitWebConfig(locations = ["/test-config.xml"]) // <1>
|
2017-09-27 22:25:16 +08:00
|
|
|
|
class XmlJUnitJupiterSpringWebTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-19 22:37:50 +08:00
|
|
|
|
<1> Specify the location of a configuration file.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
See <<testcontext-ctx-management>> as well as the javadoc for
|
2018-09-18 22:42:09 +08:00
|
|
|
|
{api-spring-framework}/test/context/junit/jupiter/web/SpringJUnitWebConfig.html[`@SpringJUnitWebConfig`],
|
|
|
|
|
{api-spring-framework}/test/context/ContextConfiguration.html[`@ContextConfiguration`], and
|
|
|
|
|
{api-spring-framework}/test/context/web/WebAppConfiguration.html[`@WebAppConfiguration`]
|
|
|
|
|
for further details.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-05-09 22:07:21 +08:00
|
|
|
|
[[integration-testing-annotations-testconstructor]]
|
|
|
|
|
===== `@TestConstructor`
|
|
|
|
|
|
2019-07-12 00:00:52 +08:00
|
|
|
|
`@TestConstructor` is a type-level annotation that is used to configure how the parameters
|
|
|
|
|
of a test class constructor are autowired from components in the test's
|
2019-05-09 22:07:21 +08:00
|
|
|
|
`ApplicationContext`.
|
|
|
|
|
|
|
|
|
|
If `@TestConstructor` is not present or meta-present on a test class, the default _test
|
2019-07-12 00:00:52 +08:00
|
|
|
|
constructor autowire mode_ will be used. See the tip below for details on how to change
|
2019-05-09 22:07:21 +08:00
|
|
|
|
the default mode. Note, however, that a local declaration of `@Autowired` on a
|
|
|
|
|
constructor takes precedence over both `@TestConstructor` and the default mode.
|
|
|
|
|
|
2019-07-12 00:00:52 +08:00
|
|
|
|
.Changing the default test constructor autowire mode
|
2019-05-09 22:07:21 +08:00
|
|
|
|
[TIP]
|
|
|
|
|
=====
|
2019-07-12 00:00:52 +08:00
|
|
|
|
The default _test constructor autowire mode_ can be changed by setting the
|
|
|
|
|
`spring.test.constructor.autowire.mode` JVM system property to `all`. Alternatively, the
|
|
|
|
|
default mode may be changed via the `SpringProperties` mechanism.
|
2019-05-09 22:07:21 +08:00
|
|
|
|
|
2019-07-12 00:00:52 +08:00
|
|
|
|
If the `spring.test.constructor.autowire.mode` property is not set, test class
|
|
|
|
|
constructors will not be automatically autowired.
|
2019-05-09 22:07:21 +08:00
|
|
|
|
=====
|
|
|
|
|
|
|
|
|
|
NOTE: As of Spring Framework 5.2, `@TestConstructor` is only supported in conjunction
|
|
|
|
|
with the `SpringExtension` for use with JUnit Jupiter. Note that the `SpringExtension` is
|
|
|
|
|
often automatically registered for you – for example, when using annotations such as
|
|
|
|
|
`@SpringJUnitConfig` and `@SpringJUnitWebConfig` or various test-related annotations from
|
|
|
|
|
Spring Boot Test.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
[[integration-testing-annotations-junit-jupiter-enabledif]]
|
|
|
|
|
===== `@EnabledIf`
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
|
|
|
|
`@EnabledIf` is used to signal that the annotated JUnit Jupiter test class or test method
|
2018-08-30 23:29:17 +08:00
|
|
|
|
is enabled and should be run if the supplied `expression` evaluates to `true`.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Specifically, if the expression evaluates to `Boolean.TRUE` or a `String` equal to `true`
|
|
|
|
|
(ignoring case), the test is enabled. When applied at the class level, all test methods
|
|
|
|
|
within that class are automatically enabled by default as well.
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Expressions can be any of the following:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
* <<core.adoc#expressions, Spring Expression Language>> (SpEL) expression. For example:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")`
|
2019-03-05 20:08:34 +08:00
|
|
|
|
* Placeholder for a property available in the Spring <<core.adoc#beans-environment, `Environment`>>.
|
|
|
|
|
For example: `@EnabledIf("${smoke.tests.enabled}")`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Text literal. For example: `@EnabledIf("true")`
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Note, however, that a text literal that is not the result of dynamic resolution of a
|
2018-09-18 22:42:09 +08:00
|
|
|
|
property placeholder is of zero practical value, since `@EnabledIf("false")` is
|
|
|
|
|
equivalent to `@Disabled` and `@EnabledIf("true")` is logically meaningless.
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can use `@EnabledIf` as a meta-annotation to create custom composed annotations. For
|
|
|
|
|
example, you can create a custom `@EnabledOnMac` annotation as follows:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
|
|
|
@EnabledIf(
|
|
|
|
|
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
|
|
|
|
|
reason = "Enabled on Mac OS"
|
|
|
|
|
)
|
|
|
|
|
public @interface EnabledOnMac {}
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
|
|
|
|
|
@Retention(AnnotationRetention.RUNTIME)
|
|
|
|
|
@EnabledIf(
|
|
|
|
|
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
|
|
|
|
|
reason = "Enabled on Mac OS"
|
|
|
|
|
)
|
|
|
|
|
annotation class EnabledOnMac {}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-21 20:45:25 +08:00
|
|
|
|
[[integration-testing-annotations-junit-jupiter-disabledif]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== `@DisabledIf`
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
|
|
|
|
`@DisabledIf` is used to signal that the annotated JUnit Jupiter test class or test
|
2018-08-30 23:29:17 +08:00
|
|
|
|
method is disabled and should not be executed if the supplied `expression` evaluates to
|
2017-09-27 22:25:16 +08:00
|
|
|
|
`true`. Specifically, if the expression evaluates to `Boolean.TRUE` or a `String` equal
|
2018-09-18 22:42:09 +08:00
|
|
|
|
to `true` (ignoring case), the test is disabled. When applied at the class level, all
|
|
|
|
|
test methods within that class are automatically disabled as well.
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Expressions can be any of the following:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
* <<core.adoc#expressions, Spring Expression Language>> (SpEL) expression. For example:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")`
|
2019-03-05 20:08:34 +08:00
|
|
|
|
* Placeholder for a property available in the Spring <<core.adoc#beans-environment, `Environment`>>.
|
|
|
|
|
For example: `@DisabledIf("${smoke.tests.disabled}")`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Text literal. For example: `@DisabledIf("true")`
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Note, however, that a text literal that is not the result of dynamic resolution of a
|
|
|
|
|
property placeholder is of zero practical value, since `@DisabledIf("true")` is
|
2017-09-27 22:25:16 +08:00
|
|
|
|
equivalent to `@Disabled` and `@DisabledIf("false")` is logically meaningless.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can use `@DisabledIf` as a meta-annotation to create custom composed annotations. For
|
|
|
|
|
example, you can create a custom `@DisabledOnMac` annotation as follows:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
|
|
|
@DisabledIf(
|
|
|
|
|
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
|
|
|
|
|
reason = "Disabled on Mac OS"
|
|
|
|
|
)
|
|
|
|
|
public @interface DisabledOnMac {}
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
|
|
|
|
|
@Retention(AnnotationRetention.RUNTIME)
|
|
|
|
|
@DisabledIf(
|
|
|
|
|
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
|
|
|
|
|
reason = "Disabled on Mac OS"
|
|
|
|
|
)
|
|
|
|
|
annotation class DisabledOnMac {}
|
|
|
|
|
----
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[integration-testing-annotations-meta]]
|
|
|
|
|
==== Meta-Annotation Support for Testing
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can use most test-related annotations as
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<core.adoc#beans-meta-annotations, meta-annotations>> to create custom composed
|
2018-08-30 23:29:17 +08:00
|
|
|
|
annotations and reduce configuration duplication across a test suite.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can use each of the following as a meta-annotation in conjunction with the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<testcontext-framework, TestContext framework>>.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2016-06-10 00:55:36 +08:00
|
|
|
|
* `@BootstrapWith`
|
2015-03-03 18:38:01 +08:00
|
|
|
|
* `@ContextConfiguration`
|
|
|
|
|
* `@ContextHierarchy`
|
|
|
|
|
* `@ActiveProfiles`
|
|
|
|
|
* `@TestPropertySource`
|
|
|
|
|
* `@DirtiesContext`
|
|
|
|
|
* `@WebAppConfiguration`
|
|
|
|
|
* `@TestExecutionListeners`
|
|
|
|
|
* `@Transactional`
|
|
|
|
|
* `@BeforeTransaction`
|
|
|
|
|
* `@AfterTransaction`
|
2016-04-06 19:55:19 +08:00
|
|
|
|
* `@Commit`
|
2015-03-03 18:38:01 +08:00
|
|
|
|
* `@Rollback`
|
|
|
|
|
* `@Sql`
|
|
|
|
|
* `@SqlConfig`
|
2019-07-21 21:37:51 +08:00
|
|
|
|
* `@SqlMergeMode`
|
2015-03-03 18:38:01 +08:00
|
|
|
|
* `@SqlGroup`
|
2017-09-28 03:04:58 +08:00
|
|
|
|
* `@Repeat` _(only supported on JUnit 4)_
|
|
|
|
|
* `@Timed` _(only supported on JUnit 4)_
|
|
|
|
|
* `@IfProfileValue` _(only supported on JUnit 4)_
|
|
|
|
|
* `@ProfileValueSourceConfiguration` _(only supported on JUnit 4)_
|
|
|
|
|
* `@SpringJUnitConfig` _(only supported on JUnit Jupiter)_
|
|
|
|
|
* `@SpringJUnitWebConfig` _(only supported on JUnit Jupiter)_
|
2019-05-09 22:07:21 +08:00
|
|
|
|
* `@TestConstructor` _(only supported on JUnit Jupiter)_
|
2017-09-28 03:04:58 +08:00
|
|
|
|
* `@EnabledIf` _(only supported on JUnit Jupiter)_
|
|
|
|
|
* `@DisabledIf` _(only supported on JUnit Jupiter)_
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Consider the following example:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2016-02-28 06:02:55 +08:00
|
|
|
|
@RunWith(SpringRunner.class)
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
|
|
|
|
@ActiveProfiles("dev")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Transactional
|
|
|
|
|
public class OrderRepositoryTests { }
|
|
|
|
|
|
2016-02-28 06:02:55 +08:00
|
|
|
|
@RunWith(SpringRunner.class)
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
|
|
|
|
@ActiveProfiles("dev")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Transactional
|
|
|
|
|
public class UserRepositoryTests { }
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@RunWith(SpringRunner::class)
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
class OrderRepositoryTests { }
|
|
|
|
|
|
|
|
|
|
@RunWith(SpringRunner::class)
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
class UserRepositoryTests { }
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If we discover that we are repeating the preceding configuration across our JUnit 4-based
|
|
|
|
|
test suite, we can reduce the duplication by introducing a custom composed annotation
|
|
|
|
|
that centralizes the common test configuration for Spring, as follows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Target(ElementType.TYPE)
|
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
|
|
|
|
@ActiveProfiles("dev")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Transactional
|
2017-09-27 22:25:16 +08:00
|
|
|
|
public @interface TransactionalDevTestConfig { }
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Target(AnnotationTarget.TYPE)
|
|
|
|
|
@Retention(AnnotationRetention.RUNTIME)
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
annotation class TransactionalDevTestConfig { }
|
|
|
|
|
----
|
|
|
|
|
|
2017-09-27 22:25:16 +08:00
|
|
|
|
Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
configuration of individual JUnit 4 based test classes, as follows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2016-02-28 06:02:55 +08:00
|
|
|
|
@RunWith(SpringRunner.class)
|
2017-09-27 22:25:16 +08:00
|
|
|
|
@TransactionalDevTestConfig
|
2015-03-03 18:38:01 +08:00
|
|
|
|
public class OrderRepositoryTests { }
|
|
|
|
|
|
2016-02-28 06:02:55 +08:00
|
|
|
|
@RunWith(SpringRunner.class)
|
2017-09-27 22:25:16 +08:00
|
|
|
|
@TransactionalDevTestConfig
|
2015-03-03 18:38:01 +08:00
|
|
|
|
public class UserRepositoryTests { }
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@RunWith(SpringRunner::class)
|
|
|
|
|
@TransactionalDevTestConfig
|
|
|
|
|
class OrderRepositoryTests
|
|
|
|
|
|
|
|
|
|
@RunWith(SpringRunner::class)
|
|
|
|
|
@TransactionalDevTestConfig
|
|
|
|
|
class UserRepositoryTests
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 21:38:06 +08:00
|
|
|
|
If we write tests that use JUnit Jupiter, we can reduce code duplication even further,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
since annotations in JUnit 5 can also be used as meta-annotations. Consider the following
|
|
|
|
|
example:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension.class)
|
|
|
|
|
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
class OrderRepositoryTests { }
|
|
|
|
|
|
|
|
|
|
@ExtendWith(SpringExtension.class)
|
|
|
|
|
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
class UserRepositoryTests { }
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
class OrderRepositoryTests { }
|
|
|
|
|
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
class UserRepositoryTests { }
|
|
|
|
|
----
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If we discover that we are repeating the preceding configuration across our JUnit
|
|
|
|
|
Jupiter-based test suite, we can reduce the duplication by introducing a custom composed
|
|
|
|
|
annotation that centralizes the common test configuration for Spring and JUnit Jupiter,
|
|
|
|
|
as follows:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
|
|
|
|
@Target(ElementType.TYPE)
|
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
|
|
|
@ExtendWith(SpringExtension.class)
|
|
|
|
|
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
public @interface TransactionalDevTestConfig { }
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Target(AnnotationTarget.TYPE)
|
|
|
|
|
@Retention(AnnotationRetention.RUNTIME)
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
@Transactional
|
|
|
|
|
annotation class TransactionalDevTestConfig { }
|
|
|
|
|
----
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
|
|
|
|
Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
configuration of individual JUnit Jupiter based test classes, as follows:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@TransactionalDevTestConfig
|
|
|
|
|
class OrderRepositoryTests { }
|
|
|
|
|
|
|
|
|
|
@TransactionalDevTestConfig
|
|
|
|
|
class UserRepositoryTests { }
|
|
|
|
|
----
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
|
|
|
|
@TransactionalDevTestConfig
|
|
|
|
|
class OrderRepositoryTests { }
|
|
|
|
|
|
|
|
|
|
@TransactionalDevTestConfig
|
|
|
|
|
class UserRepositoryTests { }
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
Since JUnit Jupiter supports the use of `@Test`, `@RepeatedTest`, `ParameterizedTest`,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
and others as meta-annotations, you can also create custom composed annotations at the
|
|
|
|
|
test method level. For example, if we wish to create a composed annotation that combines
|
|
|
|
|
the `@Test` and `@Tag` annotations from JUnit Jupiter with the `@Transactional`
|
2018-08-30 23:29:17 +08:00
|
|
|
|
annotation from Spring, we could create an `@TransactionalIntegrationTest` annotation, as
|
|
|
|
|
follows:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
|
|
|
|
@Target(ElementType.METHOD)
|
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
|
|
|
@Transactional
|
|
|
|
|
@Tag("integration-test") // org.junit.jupiter.api.Tag
|
|
|
|
|
@Test // org.junit.jupiter.api.Test
|
|
|
|
|
public @interface TransactionalIntegrationTest { }
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Target(AnnotationTarget.TYPE)
|
|
|
|
|
@Retention(AnnotationRetention.RUNTIME)
|
|
|
|
|
@Transactional
|
|
|
|
|
@Tag("integration-test") // org.junit.jupiter.api.Tag
|
|
|
|
|
@Test // org.junit.jupiter.api.Test
|
|
|
|
|
annotation class TransactionalIntegrationTest { }
|
|
|
|
|
----
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
|
|
|
|
Then we can use our custom `@TransactionalIntegrationTest` annotation to simplify the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
configuration of individual JUnit Jupiter based test methods, as follows:
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-27 22:25:16 +08:00
|
|
|
|
----
|
|
|
|
|
@TransactionalIntegrationTest
|
|
|
|
|
void saveOrder() { }
|
|
|
|
|
|
|
|
|
|
@TransactionalIntegrationTest
|
|
|
|
|
void deleteOrder() { }
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@TransactionalIntegrationTest
|
|
|
|
|
fun saveOrder() { }
|
|
|
|
|
|
|
|
|
|
@TransactionalIntegrationTest
|
|
|
|
|
fun deleteOrder() { }
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-21 20:45:25 +08:00
|
|
|
|
For further details, see the
|
|
|
|
|
https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[Spring Annotation Programming Model]
|
|
|
|
|
wiki page.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-framework]]
|
|
|
|
|
=== Spring TestContext Framework
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The Spring TestContext Framework (located in the `org.springframework.test.context`
|
|
|
|
|
package) provides generic, annotation-driven unit and integration testing support that is
|
|
|
|
|
agnostic of the testing framework in use. The TestContext framework also places a great
|
|
|
|
|
deal of importance on convention over configuration, with reasonable defaults that you
|
|
|
|
|
can override through annotation-based configuration.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
In addition to generic testing infrastructure, the TestContext framework provides
|
2018-09-18 22:42:09 +08:00
|
|
|
|
explicit support for JUnit 4, JUnit Jupiter (AKA JUnit 5), and TestNG. For JUnit 4 and
|
|
|
|
|
TestNG, Spring provides `abstract` support classes. Furthermore, Spring provides a custom
|
|
|
|
|
JUnit `Runner` and custom JUnit `Rules` for JUnit 4 and a custom `Extension` for JUnit
|
|
|
|
|
Jupiter that let you write so-called POJO test classes. POJO test classes are not
|
|
|
|
|
required to extend a particular class hierarchy, such as the `abstract` support classes.
|
2017-09-27 22:25:16 +08:00
|
|
|
|
|
|
|
|
|
The following section provides an overview of the internals of the TestContext framework.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If you are interested only in using the framework and are not interested in extending it
|
|
|
|
|
with your own custom listeners or custom loaders, feel free to go directly to the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
configuration (<<testcontext-ctx-management, context management>>,
|
|
|
|
|
<<testcontext-fixture-di, dependency injection>>, <<testcontext-tx,transaction
|
|
|
|
|
management>>), <<testcontext-support-classes, support classes>>, and
|
|
|
|
|
<<integration-testing-annotations, annotation support>> sections.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[testcontext-key-abstractions]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Key Abstractions
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-04-06 20:56:08 +08:00
|
|
|
|
The core of the framework consists of the `TestContextManager` class and the
|
|
|
|
|
`TestContext`, `TestExecutionListener`, and `SmartContextLoader` interfaces. A
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`TestContextManager` is created for each test class (for example, for the execution of
|
|
|
|
|
all test methods within a single test class in JUnit Jupiter). The `TestContextManager`,
|
|
|
|
|
in turn, manages a `TestContext` that holds the context of the current test. The
|
2017-09-27 22:25:16 +08:00
|
|
|
|
`TestContextManager` also updates the state of the `TestContext` as the test progresses
|
|
|
|
|
and delegates to `TestExecutionListener` implementations, which instrument the actual
|
|
|
|
|
test execution by providing dependency injection, managing transactions, and so on. A
|
|
|
|
|
`SmartContextLoader` is responsible for loading an `ApplicationContext` for a given test
|
2018-10-25 21:15:58 +08:00
|
|
|
|
class. See the {api-spring-framework}/test/context/package-summary.html[javadoc] and the
|
2018-09-21 20:45:25 +08:00
|
|
|
|
Spring test suite for further information and examples of various implementations.
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== `TestContext`
|
|
|
|
|
|
|
|
|
|
`TestContext` encapsulates the context in which a test is executed (agnostic of the
|
|
|
|
|
actual testing framework in use) and provides context management and caching support for
|
2016-06-09 23:03:44 +08:00
|
|
|
|
the test instance for which it is responsible. The `TestContext` also delegates to a
|
|
|
|
|
`SmartContextLoader` to load an `ApplicationContext` if requested.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== `TestContextManager`
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`TestContextManager` is the main entry point into the Spring TestContext Framework and is
|
|
|
|
|
responsible for managing a single `TestContext` and signaling events to each registered
|
|
|
|
|
`TestExecutionListener` at well-defined test execution points:
|
2016-06-09 23:03:44 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* Prior to any "`before class`" or "`before all`" methods of a particular testing framework.
|
|
|
|
|
* Test instance post-processing.
|
|
|
|
|
* Prior to any "`before`" or "`before each`" methods of a particular testing framework.
|
|
|
|
|
* Immediately before execution of the test method but after test setup.
|
|
|
|
|
* Immediately after execution of the test method but before test tear down.
|
|
|
|
|
* After any "`after`" or "`after each`" methods of a particular testing framework.
|
|
|
|
|
* After any "`after class`" or "`after all`" methods of a particular testing framework.
|
2016-06-09 23:03:44 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== `TestExecutionListener`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
`TestExecutionListener` defines the API for reacting to test-execution events published by
|
|
|
|
|
the `TestContextManager` with which the listener is registered. See <<testcontext-tel-config>>.
|
2016-06-09 23:03:44 +08:00
|
|
|
|
|
|
|
|
|
===== Context Loaders
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
`ContextLoader` is a strategy interface for loading an `ApplicationContext` for an
|
|
|
|
|
integration test managed by the Spring TestContext Framework. You should implement
|
2019-09-25 19:29:17 +08:00
|
|
|
|
`SmartContextLoader` instead of this interface to provide support for component classes,
|
2019-03-05 20:08:34 +08:00
|
|
|
|
active bean definition profiles, test property sources, context hierarchies, and
|
|
|
|
|
`WebApplicationContext` support.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-09-26 21:02:30 +08:00
|
|
|
|
`SmartContextLoader` is an extension of the `ContextLoader` interface that supersedes the
|
|
|
|
|
original minimal `ContextLoader` SPI. Specifically, a `SmartContextLoader` can choose to
|
|
|
|
|
process resource locations, component classes, or context initializers. Furthermore, a
|
|
|
|
|
`SmartContextLoader` can set active bean definition profiles and test property sources in
|
|
|
|
|
the context that it loads.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Spring provides the following implementations:
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* `DelegatingSmartContextLoader`: One of two default loaders, it delegates internally to
|
|
|
|
|
an `AnnotationConfigContextLoader`, a `GenericXmlContextLoader`, or a
|
|
|
|
|
`GenericGroovyXmlContextLoader`, depending either on the configuration declared for the
|
|
|
|
|
test class or on the presence of default locations or default configuration classes.
|
|
|
|
|
Groovy support is enabled only if Groovy is on the classpath.
|
|
|
|
|
* `WebDelegatingSmartContextLoader`: One of two default loaders, it delegates internally
|
|
|
|
|
to an `AnnotationConfigWebContextLoader`, a `GenericXmlWebContextLoader`, or a
|
|
|
|
|
`GenericGroovyXmlWebContextLoader`, depending either on the configuration declared for
|
|
|
|
|
the test class or on the presence of default locations or default configuration
|
|
|
|
|
classes. A web `ContextLoader` is used only if `@WebAppConfiguration` is present on the
|
|
|
|
|
test class. Groovy support is enabled only if Groovy is on the classpath.
|
2019-09-25 19:29:17 +08:00
|
|
|
|
* `AnnotationConfigContextLoader`: Loads a standard `ApplicationContext` from component
|
2018-09-18 22:42:09 +08:00
|
|
|
|
classes.
|
2019-09-25 19:29:17 +08:00
|
|
|
|
* `AnnotationConfigWebContextLoader`: Loads a `WebApplicationContext` from component
|
2018-09-18 22:42:09 +08:00
|
|
|
|
classes.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `GenericGroovyXmlContextLoader`: Loads a standard `ApplicationContext` from resource
|
2018-09-18 22:42:09 +08:00
|
|
|
|
locations that are either Groovy scripts or XML configuration files.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `GenericGroovyXmlWebContextLoader`: Loads a `WebApplicationContext` from resource
|
2018-09-18 22:42:09 +08:00
|
|
|
|
locations that are either Groovy scripts or XML configuration files.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `GenericXmlContextLoader`: Loads a standard `ApplicationContext` from XML resource
|
2018-09-18 22:42:09 +08:00
|
|
|
|
locations.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `GenericXmlWebContextLoader`: Loads a `WebApplicationContext` from XML resource
|
2018-09-18 22:42:09 +08:00
|
|
|
|
locations.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `GenericPropertiesContextLoader`: Loads a standard `ApplicationContext` from Java
|
2018-09-18 22:42:09 +08:00
|
|
|
|
properties files.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2016-06-10 00:55:36 +08:00
|
|
|
|
[[testcontext-bootstrapping]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Bootstrapping the TestContext Framework
|
2016-06-10 00:55:36 +08:00
|
|
|
|
|
|
|
|
|
The default configuration for the internals of the Spring TestContext Framework is
|
|
|
|
|
sufficient for all common use cases. However, there are times when a development team or
|
|
|
|
|
third party framework would like to change the default `ContextLoader`, implement a
|
|
|
|
|
custom `TestContext` or `ContextCache`, augment the default sets of
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ContextCustomizerFactory` and `TestExecutionListener` implementations, and so on. For
|
|
|
|
|
such low-level control over how the TestContext framework operates, Spring provides a
|
2016-06-10 00:55:36 +08:00
|
|
|
|
bootstrapping strategy.
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`TestContextBootstrapper` defines the SPI for bootstrapping the TestContext framework. A
|
|
|
|
|
`TestContextBootstrapper` is used by the `TestContextManager` to load the
|
2016-06-10 00:55:36 +08:00
|
|
|
|
`TestExecutionListener` implementations for the current test and to build the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`TestContext` that it manages. You can configure a custom bootstrapping strategy for a
|
|
|
|
|
test class (or test class hierarchy) by using `@BootstrapWith`, either directly or as a
|
2018-09-18 22:42:09 +08:00
|
|
|
|
meta-annotation. If a bootstrapper is not explicitly configured by using
|
|
|
|
|
`@BootstrapWith`, either the `DefaultTestContextBootstrapper` or the
|
|
|
|
|
`WebTestContextBootstrapper` is used, depending on the presence of `@WebAppConfiguration`.
|
2016-06-10 00:55:36 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Since the `TestContextBootstrapper` SPI is likely to change in the future (to accommodate
|
|
|
|
|
new requirements), we strongly encourage implementers not to implement this interface
|
|
|
|
|
directly but rather to extend `AbstractTestContextBootstrapper` or one of its concrete
|
|
|
|
|
subclasses instead.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-tel-config]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== `TestExecutionListener` Configuration
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Spring provides the following `TestExecutionListener` implementations that are registered
|
2019-04-06 21:33:47 +08:00
|
|
|
|
by default, exactly in the following order:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* `ServletTestExecutionListener`: Configures Servlet API mocks for a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`WebApplicationContext`.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* `DirtiesContextBeforeModesTestExecutionListener`: Handles the `@DirtiesContext`
|
|
|
|
|
annotation for "`before`" modes.
|
|
|
|
|
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
|
2018-08-30 23:29:17 +08:00
|
|
|
|
instance.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
|
2018-08-30 23:29:17 +08:00
|
|
|
|
"`after`" modes.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* `TransactionalTestExecutionListener`: Provides transactional test execution with
|
2018-08-30 23:29:17 +08:00
|
|
|
|
default rollback semantics.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* `SqlScriptsTestExecutionListener`: Runs SQL scripts configured by using the `@Sql`
|
2018-08-30 23:29:17 +08:00
|
|
|
|
annotation.
|
2019-04-06 21:33:47 +08:00
|
|
|
|
* `EventPublishingTestExecutionListener`: Publishes test execution events to the test's
|
|
|
|
|
`ApplicationContext` (see <<testcontext-test-execution-events>>).
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-tel-config-registering-tels]]
|
2019-04-04 00:18:39 +08:00
|
|
|
|
===== Registering `TestExecutionListener` Implementations
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-04-04 00:18:39 +08:00
|
|
|
|
You can register `TestExecutionListener` implementations for a test class and its
|
|
|
|
|
subclasses by using the `@TestExecutionListeners` annotation. See
|
|
|
|
|
<<integration-testing-annotations, annotation support>> and the javadoc for
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/test/context/TestExecutionListeners.html[`@TestExecutionListeners`]
|
|
|
|
|
for details and examples.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-tel-config-automatic-discovery]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Automatic Discovery of Default `TestExecutionListener` Implementations
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-04-04 00:18:39 +08:00
|
|
|
|
Registering `TestExecutionListener` implementations by using `@TestExecutionListeners` is
|
|
|
|
|
suitable for custom listeners that are used in limited testing scenarios. However, it can
|
2019-09-26 21:02:30 +08:00
|
|
|
|
become cumbersome if a custom listener needs to be used across an entire test suite. This
|
|
|
|
|
issue is addressed through support for automatic discovery of default
|
2019-04-04 00:18:39 +08:00
|
|
|
|
`TestExecutionListener` implementations through the `SpringFactoriesLoader` mechanism.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-04-06 21:33:47 +08:00
|
|
|
|
Specifically, the `spring-test` module declares all core default `TestExecutionListener`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
implementations under the `org.springframework.test.context.TestExecutionListener` key in
|
|
|
|
|
its `META-INF/spring.factories` properties file. Third-party frameworks and developers
|
|
|
|
|
can contribute their own `TestExecutionListener` implementations to the list of default
|
|
|
|
|
listeners in the same manner through their own `META-INF/spring.factories` properties
|
|
|
|
|
file.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-tel-config-ordering]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Ordering `TestExecutionListener` Implementations
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
When the TestContext framework discovers default `TestExecutionListener` implementations
|
2019-03-05 20:08:34 +08:00
|
|
|
|
through the <<testcontext-tel-config-automatic-discovery, aforementioned>>
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`SpringFactoriesLoader` mechanism, the instantiated listeners are sorted by using
|
|
|
|
|
Spring's `AnnotationAwareOrderComparator`, which honors Spring's `Ordered` interface and
|
|
|
|
|
`@Order` annotation for ordering. `AbstractTestExecutionListener` and all default
|
|
|
|
|
`TestExecutionListener` implementations provided by Spring implement `Ordered` with
|
|
|
|
|
appropriate values. Third-party frameworks and developers should therefore make sure that
|
|
|
|
|
their default `TestExecutionListener` implementations are registered in the proper order
|
2018-10-25 21:15:58 +08:00
|
|
|
|
by implementing `Ordered` or declaring `@Order`. See the javadoc for the `getOrder()`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
methods of the core default `TestExecutionListener` implementations for details on what
|
|
|
|
|
values are assigned to each core listener.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-tel-config-merging]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Merging `TestExecutionListener` Implementations
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
If a custom `TestExecutionListener` is registered via `@TestExecutionListeners`, the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
default listeners are not registered. In most common testing scenarios, this effectively
|
|
|
|
|
forces the developer to manually declare all default listeners in addition to any custom
|
|
|
|
|
listeners. The following listing demonstrates this style of configuration:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestExecutionListeners({
|
|
|
|
|
MyCustomTestExecutionListener.class,
|
|
|
|
|
ServletTestExecutionListener.class,
|
2016-04-06 19:55:19 +08:00
|
|
|
|
DirtiesContextBeforeModesTestExecutionListener.class,
|
2015-03-03 18:38:01 +08:00
|
|
|
|
DependencyInjectionTestExecutionListener.class,
|
|
|
|
|
DirtiesContextTestExecutionListener.class,
|
|
|
|
|
TransactionalTestExecutionListener.class,
|
|
|
|
|
SqlScriptsTestExecutionListener.class
|
|
|
|
|
})
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestExecutionListeners(
|
|
|
|
|
MyCustomTestExecutionListener::class,
|
|
|
|
|
ServletTestExecutionListener::class,
|
|
|
|
|
DirtiesContextBeforeModesTestExecutionListener::class,
|
|
|
|
|
DependencyInjectionTestExecutionListener::class,
|
|
|
|
|
DirtiesContextTestExecutionListener::class,
|
|
|
|
|
TransactionalTestExecutionListener::class,
|
|
|
|
|
SqlScriptsTestExecutionListener::class
|
|
|
|
|
)
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The challenge with this approach is that it requires that the developer know exactly
|
|
|
|
|
which listeners are registered by default. Moreover, the set of default listeners can
|
|
|
|
|
change from release to release -- for example, `SqlScriptsTestExecutionListener` was
|
2016-04-06 20:45:28 +08:00
|
|
|
|
introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener`
|
|
|
|
|
was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring
|
2019-09-26 21:02:30 +08:00
|
|
|
|
Boot and Spring Security register their own default `TestExecutionListener`
|
|
|
|
|
implementations by using the aforementioned <<testcontext-tel-config-automatic-discovery,
|
|
|
|
|
automatic discovery mechanism>>.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
To avoid having to be aware of and re-declare all default listeners, you can set the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`.
|
|
|
|
|
`MERGE_WITH_DEFAULTS` indicates that locally declared listeners should be merged with the
|
|
|
|
|
default listeners. The merging algorithm ensures that duplicates are removed from the
|
|
|
|
|
list and that the resulting set of merged listeners is sorted according to the semantics
|
|
|
|
|
of `AnnotationAwareOrderComparator`, as described in <<testcontext-tel-config-ordering>>.
|
|
|
|
|
If a listener implements `Ordered` or is annotated with `@Order`, it can influence the
|
|
|
|
|
position in which it is merged with the defaults. Otherwise, locally declared listeners
|
|
|
|
|
are appended to the list of default listeners when merged.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
For example, if the `MyCustomTestExecutionListener` class in the previous example
|
|
|
|
|
configures its `order` value (for example, `500`) to be less than the order of the
|
|
|
|
|
`ServletTestExecutionListener` (which happens to be `1000`), the
|
|
|
|
|
`MyCustomTestExecutionListener` can then be automatically merged with the list of
|
2018-08-30 23:29:17 +08:00
|
|
|
|
defaults in front of the `ServletTestExecutionListener`, and the previous example could
|
|
|
|
|
be replaced with the following:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestExecutionListeners(
|
|
|
|
|
listeners = MyCustomTestExecutionListener.class,
|
2015-08-31 01:01:28 +08:00
|
|
|
|
mergeMode = MERGE_WITH_DEFAULTS
|
2015-03-03 18:38:01 +08:00
|
|
|
|
)
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestExecutionListeners(
|
|
|
|
|
listeners = [MyCustomTestExecutionListener::class],
|
|
|
|
|
mergeMode = MERGE_WITH_DEFAULTS
|
|
|
|
|
)
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-04-04 00:18:39 +08:00
|
|
|
|
[[testcontext-test-execution-events]]
|
|
|
|
|
==== Test Execution Events
|
|
|
|
|
|
|
|
|
|
The `EventPublishingTestExecutionListener` introduced in Spring Framework 5.2 offers an
|
2019-04-06 21:33:47 +08:00
|
|
|
|
alternative approach to implementing a custom `TestExecutionListener`. Components in the
|
|
|
|
|
test's `ApplicationContext` can listen to the following events published by the
|
|
|
|
|
`EventPublishingTestExecutionListener`, each of which corresponds to a method in the
|
|
|
|
|
`TestExecutionListener` API.
|
2019-04-04 00:18:39 +08:00
|
|
|
|
|
|
|
|
|
* `BeforeTestClassEvent`
|
|
|
|
|
* `PrepareTestInstanceEvent`
|
|
|
|
|
* `BeforeTestMethodEvent`
|
|
|
|
|
* `BeforeTestExecutionEvent`
|
|
|
|
|
* `AfterTestExecutionEvent`
|
|
|
|
|
* `AfterTestMethodEvent`
|
|
|
|
|
* `AfterTestClassEvent`
|
|
|
|
|
|
2019-04-06 21:33:47 +08:00
|
|
|
|
NOTE: These events are only published if the `ApplicationContext` has already been loaded.
|
|
|
|
|
|
2019-04-04 00:18:39 +08:00
|
|
|
|
These events may be consumed for various reasons, such as resetting mock beans or tracing
|
|
|
|
|
test execution. One advantage of consuming test execution events rather than implementing
|
|
|
|
|
a custom `TestExecutionListener` is that test execution events may be consumed by any
|
|
|
|
|
Spring bean registered in the test `ApplicationContext`, and such beans may benefit
|
|
|
|
|
directly from dependency injection and other features of the `ApplicationContext`. In
|
|
|
|
|
contrast, a `TestExecutionListener` is not a bean in the `ApplicationContext`.
|
|
|
|
|
|
|
|
|
|
In order to listen to test execution events, a Spring bean may choose to implement the
|
|
|
|
|
`org.springframework.context.ApplicationListener` interface. Alternatively, listener
|
|
|
|
|
methods can be annotated with `@EventListener` and configured to listen to one of the
|
|
|
|
|
particular event types listed above (see
|
|
|
|
|
<<core.adoc#context-functionality-events-annotation, Annotation-based Event Listeners>>).
|
|
|
|
|
Due to the popularity of this approach, Spring provides the following dedicated
|
|
|
|
|
`@EventListener` annotations to simplify registration of test execution event listeners.
|
|
|
|
|
These annotations reside in the `org.springframework.test.context.event.annotation`
|
|
|
|
|
package.
|
|
|
|
|
|
|
|
|
|
* `@BeforeTestClass`
|
|
|
|
|
* `@PrepareTestInstance`
|
|
|
|
|
* `@BeforeTestMethod`
|
|
|
|
|
* `@BeforeTestExecution`
|
|
|
|
|
* `@AfterTestExecution`
|
|
|
|
|
* `@AfterTestMethod`
|
|
|
|
|
* `@AfterTestClass`
|
|
|
|
|
|
|
|
|
|
[[testcontext-test-execution-events-exception-handling]]
|
|
|
|
|
===== Exception Handling
|
|
|
|
|
|
|
|
|
|
By default, if a test execution event listener throws an exception while consuming an
|
|
|
|
|
event, that exception will propagate to the underlying testing framework in use (such as
|
|
|
|
|
JUnit or TestNG). For example, if the consumption of a `BeforeTestMethodEvent` results in
|
|
|
|
|
an exception, the corresponding test method will fail as a result of the exception. In
|
|
|
|
|
contrast, if an asynchronous test execution event listener throws an exception, the
|
|
|
|
|
exception will not propagate to the underlying testing framework. For further details on
|
|
|
|
|
asynchronous exception handling, consult the class-level javadoc for `@EventListener`.
|
|
|
|
|
|
|
|
|
|
[[testcontext-test-execution-events-async]]
|
|
|
|
|
===== Asynchronous Listeners
|
|
|
|
|
|
|
|
|
|
If you want a particular test execution event listener to process events asynchronously,
|
|
|
|
|
you can use Spring's <<integration.adoc#scheduling-annotation-support-async,regular
|
|
|
|
|
`@Async` support>>. For further details, consult the class-level javadoc for
|
|
|
|
|
`@EventListener`.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-ctx-management]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Context Management
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Each `TestContext` provides context management and caching support for the test instance
|
2018-08-30 23:29:17 +08:00
|
|
|
|
for which it is responsible. Test instances do not automatically receive access to the
|
2015-03-03 18:38:01 +08:00
|
|
|
|
configured `ApplicationContext`. However, if a test class implements the
|
|
|
|
|
`ApplicationContextAware` interface, a reference to the `ApplicationContext` is supplied
|
|
|
|
|
to the test instance. Note that `AbstractJUnit4SpringContextTests` and
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`AbstractTestNGSpringContextTests` implement `ApplicationContextAware` and, therefore,
|
2015-03-03 18:38:01 +08:00
|
|
|
|
provide access to the `ApplicationContext` automatically.
|
|
|
|
|
|
|
|
|
|
.@Autowired ApplicationContext
|
|
|
|
|
[TIP]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=====
|
2018-09-18 22:42:09 +08:00
|
|
|
|
As an alternative to implementing the `ApplicationContextAware` interface, you can inject
|
|
|
|
|
the application context for your test class through the `@Autowired` annotation on either
|
|
|
|
|
a field or setter method, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitConfig
|
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Autowired // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
ApplicationContext applicationContext;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Injecting the `ApplicationContext`.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig
|
|
|
|
|
class MyTest {
|
|
|
|
|
|
|
|
|
|
@Autowired // <1>
|
|
|
|
|
lateinit var applicationContext: ApplicationContext
|
|
|
|
|
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Injecting the `ApplicationContext`.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Similarly, if your test is configured to load a `WebApplicationContext`, you can inject
|
2018-08-30 23:29:17 +08:00
|
|
|
|
the web application context into your test, as follows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitWebConfig // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyWebAppTest {
|
2018-09-18 21:43:14 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Autowired // <2>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
WebApplicationContext wac;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Configuring the `WebApplicationContext`.
|
|
|
|
|
<2> Injecting the `WebApplicationContext`.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitWebConfig // <1>
|
|
|
|
|
class MyWebAppTest {
|
|
|
|
|
|
|
|
|
|
@Autowired // <2>
|
|
|
|
|
lateinit var wac: WebApplicationContext
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Configuring the `WebApplicationContext`.
|
|
|
|
|
<2> Injecting the `WebApplicationContext`.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Dependency injection by using `@Autowired` is provided by the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
`DependencyInjectionTestExecutionListener`, which is configured by default
|
|
|
|
|
(see <<testcontext-fixture-di>>).
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=====
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Test classes that use the TestContext framework do not need to extend any particular
|
|
|
|
|
class or implement a specific interface to configure their application context. Instead,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
configuration is achieved by declaring the `@ContextConfiguration` annotation at the
|
|
|
|
|
class level. If your test class does not explicitly declare application context resource
|
2019-09-25 19:29:17 +08:00
|
|
|
|
locations or component classes, the configured `ContextLoader` determines how to load a
|
2018-09-18 22:42:09 +08:00
|
|
|
|
context from a default location or default configuration classes. In addition to context
|
2019-09-25 19:29:17 +08:00
|
|
|
|
resource locations and component classes, an application context can also be configured
|
2018-09-18 22:42:09 +08:00
|
|
|
|
through application context initializers.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
The following sections explain how to use Spring's `@ContextConfiguration` annotation to
|
|
|
|
|
configure a test `ApplicationContext` by using XML configuration files, Groovy scripts,
|
2019-09-25 19:29:17 +08:00
|
|
|
|
component classes (typically `@Configuration` classes), or context initializers.
|
2016-04-06 20:36:36 +08:00
|
|
|
|
Alternatively, you can implement and configure your own custom `SmartContextLoader` for
|
|
|
|
|
advanced use cases.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* <<testcontext-ctx-management-xml>>
|
|
|
|
|
* <<testcontext-ctx-management-groovy>>
|
|
|
|
|
* <<testcontext-ctx-management-javaconfig>>
|
|
|
|
|
* <<testcontext-ctx-management-mixed-config>>
|
|
|
|
|
* <<testcontext-ctx-management-initializers>>
|
|
|
|
|
* <<testcontext-ctx-management-inheritance>>
|
|
|
|
|
* <<testcontext-ctx-management-env-profiles>>
|
|
|
|
|
* <<testcontext-ctx-management-property-sources>>
|
2020-03-24 21:07:56 +08:00
|
|
|
|
* <<testcontext-ctx-management-dynamic-property-sources>>
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* <<testcontext-ctx-management-web>>
|
|
|
|
|
* <<testcontext-ctx-management-caching>>
|
|
|
|
|
* <<testcontext-ctx-management-ctx-hierarchies>>
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-xml]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Context Configuration with XML resources
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
To load an `ApplicationContext` for your tests by using XML configuration files, annotate
|
2015-03-03 18:38:01 +08:00
|
|
|
|
your test class with `@ContextConfiguration` and configure the `locations` attribute with
|
|
|
|
|
an array that contains the resource locations of XML configuration metadata. A plain or
|
2018-09-18 22:42:09 +08:00
|
|
|
|
relative path (for example, `context.xml`) is treated as a classpath resource that is
|
|
|
|
|
relative to the package in which the test class is defined. A path starting with a slash
|
|
|
|
|
is treated as an absolute classpath location (for example, `/org/example/config.xml`). A
|
|
|
|
|
path that represents a resource URL (i.e., a path prefixed with `classpath:`, `file:`,
|
|
|
|
|
`http:`, etc.) is used _as is_.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from "/app-config.xml" and
|
|
|
|
|
// "/test-config.xml" in the root of the classpath
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) // <1>
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Setting the locations attribute to a list of XML files.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be loaded from "/app-config.xml" and
|
|
|
|
|
// "/test-config.xml" in the root of the classpath
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/test-config.xml") // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Setting the locations attribute to a list of XML files.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
`@ContextConfiguration` supports an alias for the `locations` attribute through the
|
|
|
|
|
standard Java `value` attribute. Thus, if you do not need to declare additional
|
|
|
|
|
attributes in `@ContextConfiguration`, you can omit the declaration of the `locations`
|
|
|
|
|
attribute name and declare the resource locations by using the shorthand format
|
2018-08-30 23:29:17 +08:00
|
|
|
|
demonstrated in the following example:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2018-08-30 23:29:17 +08:00
|
|
|
|
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Specifying XML files without using the `location` attribute.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/test-config.xml") // <1>
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specifying XML files without using the `location` attribute.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If you omit both the `locations` and the `value` attributes from the
|
|
|
|
|
`@ContextConfiguration` annotation, the TestContext framework tries to detect a default
|
|
|
|
|
XML resource location. Specifically, `GenericXmlContextLoader` and
|
|
|
|
|
`GenericXmlWebContextLoader` detect a default location based on the name of the test
|
|
|
|
|
class. If your class is named `com.example.MyTest`, `GenericXmlContextLoader` loads your
|
|
|
|
|
application context from `"classpath:com/example/MyTest-context.xml"`. The following
|
|
|
|
|
example shows how to do so:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from
|
|
|
|
|
// "classpath:com/example/MyTest-context.xml"
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextConfiguration // <1>
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Loading configuration from the default location.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be loaded from
|
|
|
|
|
// "classpath:com/example/MyTest-context.xml"
|
|
|
|
|
@ContextConfiguration // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Loading configuration from the default location.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-groovy]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Context Configuration with Groovy Scripts
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
To load an `ApplicationContext` for your tests by using Groovy scripts that use the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<core.adoc#groovy-bean-definition-dsl, Groovy Bean Definition DSL>>, you can annotate
|
2018-09-18 22:42:09 +08:00
|
|
|
|
your test class with `@ContextConfiguration` and configure the `locations` or `value`
|
|
|
|
|
attribute with an array that contains the resource locations of Groovy scripts. Resource
|
|
|
|
|
lookup semantics for Groovy scripts are the same as those described for
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<testcontext-ctx-management-xml, XML configuration files>>.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Enabling Groovy script support
|
2018-08-30 23:29:17 +08:00
|
|
|
|
TIP: Support for using Groovy scripts to load an `ApplicationContext` in the Spring
|
2015-03-03 18:38:01 +08:00
|
|
|
|
TestContext Framework is enabled automatically if Groovy is on the classpath.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example shows how to specify Groovy configuration files:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from "/AppConfig.groovy" and
|
|
|
|
|
// "/TestConfig.groovy" in the root of the classpath
|
2018-08-30 23:29:17 +08:00
|
|
|
|
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be loaded from "/AppConfig.groovy" and
|
|
|
|
|
// "/TestConfig.groovy" in the root of the classpath
|
|
|
|
|
@ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy") // <1>
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Specifying the location of Groovy configuration files.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
If you omit both the `locations` and `value` attributes from the `@ContextConfiguration`
|
2018-08-30 23:29:17 +08:00
|
|
|
|
annotation, the TestContext framework tries to detect a default Groovy script.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
Specifically, `GenericGroovyXmlContextLoader` and `GenericGroovyXmlWebContextLoader`
|
|
|
|
|
detect a default location based on the name of the test class. If your class is named
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`com.example.MyTest`, the Groovy context loader loads your application context from
|
|
|
|
|
`"classpath:com/example/MyTestContext.groovy"`. The following example shows how to use
|
|
|
|
|
the default:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from
|
|
|
|
|
// "classpath:com/example/MyTestContext.groovy"
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextConfiguration // <1>
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Loading configuration from the default location.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be loaded from
|
|
|
|
|
// "classpath:com/example/MyTestContext.groovy"
|
|
|
|
|
@ContextConfiguration // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Loading configuration from the default location.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
.Declaring XML configuration and Groovy scripts simultaneously
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[TIP]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=====
|
2018-09-18 22:42:09 +08:00
|
|
|
|
You can declare both XML configuration files and Groovy scripts simultaneously by using
|
|
|
|
|
the `locations` or `value` attribute of `@ContextConfiguration`. If the path to a
|
|
|
|
|
configured resource location ends with `.xml`, it is loaded by using an
|
|
|
|
|
`XmlBeanDefinitionReader`. Otherwise, it is loaded by using a
|
|
|
|
|
`GroovyBeanDefinitionReader`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following listing shows how to combine both in an integration test:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from
|
|
|
|
|
// "/app-config.xml" and "/TestConfig.groovy"
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be loaded from
|
|
|
|
|
// "/app-config.xml" and "/TestConfig.groovy"
|
|
|
|
|
@ContextConfiguration("/app-config.xml", "/TestConfig.groovy")
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=====
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-javaconfig]]
|
2019-09-25 19:29:17 +08:00
|
|
|
|
===== Context Configuration with Component Classes
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-09-25 19:29:17 +08:00
|
|
|
|
To load an `ApplicationContext` for your tests by using component classes (see
|
2018-09-18 22:42:09 +08:00
|
|
|
|
<<core.adoc#beans-java, Java-based container configuration>>), you can annotate your test
|
|
|
|
|
class with `@ContextConfiguration` and configure the `classes` attribute with an array
|
2019-09-25 19:29:17 +08:00
|
|
|
|
that contains references to component classes. The following example shows how to do so:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension.class)
|
|
|
|
|
// ApplicationContext will be loaded from AppConfig and TestConfig
|
|
|
|
|
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) // <1>
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-09-25 19:29:17 +08:00
|
|
|
|
<1> Specifying component classes.
|
2019-08-30 08:35:53 +08:00
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ExtendWith(SpringExtension::class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from AppConfig and TestConfig
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-09-26 16:55:55 +08:00
|
|
|
|
<1> Specifying component classes.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-09-25 19:29:17 +08:00
|
|
|
|
[[testcontext-ctx-management-javaconfig-component-classes]]
|
|
|
|
|
.Component Classes
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[TIP]
|
2015-03-07 14:30:52 +08:00
|
|
|
|
====
|
2019-09-25 19:29:17 +08:00
|
|
|
|
The term "`component class`" can refer to any of the following:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* A class annotated with `@Configuration`.
|
2018-09-18 21:43:14 +08:00
|
|
|
|
* A component (that is, a class annotated with `@Component`, `@Service`, `@Repository`, or other stereotype annotations).
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* A JSR-330 compliant class that is annotated with `javax.inject` annotations.
|
2019-09-25 19:29:17 +08:00
|
|
|
|
* Any class that contains `@Bean`-methods.
|
|
|
|
|
* Any other class that is intended to be registered as a Spring component (i.e., a Spring
|
|
|
|
|
bean in the `ApplicationContext`), potentially taking advantage of automatic autowiring
|
|
|
|
|
of a single constructor without the use of Spring annotations.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
See the javadoc of
|
2018-09-18 22:42:09 +08:00
|
|
|
|
{api-spring-framework}/context/annotation/Configuration.html[`@Configuration`] and
|
|
|
|
|
{api-spring-framework}/context/annotation/Bean.html[`@Bean`] for further information
|
2019-09-25 19:29:17 +08:00
|
|
|
|
regarding the configuration and semantics of component classes, paying special attention
|
2018-09-18 22:42:09 +08:00
|
|
|
|
to the discussion of `@Bean` Lite Mode.
|
2015-03-07 14:30:52 +08:00
|
|
|
|
====
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
If you omit the `classes` attribute from the `@ContextConfiguration` annotation, the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
TestContext framework tries to detect the presence of default configuration classes.
|
|
|
|
|
Specifically, `AnnotationConfigContextLoader` and `AnnotationConfigWebContextLoader`
|
|
|
|
|
detect all `static` nested classes of the test class that meet the requirements for
|
|
|
|
|
configuration class implementations, as specified in the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/context/annotation/Configuration.html[`@Configuration`] javadoc.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Note that the name of the configuration class is arbitrary. In addition, a test class can
|
|
|
|
|
contain more than one `static` nested configuration class if desired. In the following
|
|
|
|
|
example, the `OrderServiceTest` class declares a `static` nested configuration class
|
|
|
|
|
named `Config` that is automatically used to load the `ApplicationContext` for the test
|
|
|
|
|
class:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitConfig <1>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from the
|
|
|
|
|
// static nested Config class
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class OrderServiceTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Configuration
|
|
|
|
|
static class Config {
|
|
|
|
|
|
|
|
|
|
// this bean will be injected into the OrderServiceTest class
|
|
|
|
|
@Bean
|
2019-08-21 21:54:28 +08:00
|
|
|
|
OrderService orderService() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
OrderService orderService = new OrderServiceImpl();
|
|
|
|
|
// set properties, etc.
|
|
|
|
|
return orderService;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
OrderService orderService;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void testOrderService() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// test the orderService
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
<1> Loading configuration information from the nested `Config` class.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig <1>
|
|
|
|
|
// ApplicationContext will be loaded from the nested Config class
|
|
|
|
|
class OrderServiceTest {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var orderService: OrderService
|
|
|
|
|
|
|
|
|
|
@Configuration
|
|
|
|
|
class Config {
|
|
|
|
|
|
|
|
|
|
// this bean will be injected into the OrderServiceTest class
|
|
|
|
|
@Bean
|
|
|
|
|
fun orderService(): OrderService {
|
|
|
|
|
// set properties, etc.
|
|
|
|
|
return OrderServiceImpl()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testOrderService() {
|
|
|
|
|
// test the orderService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Loading configuration information from the nested `Config` class.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-mixed-config]]
|
2019-09-25 19:29:17 +08:00
|
|
|
|
===== Mixing XML, Groovy Scripts, and Component Classes
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
It may sometimes be desirable to mix XML configuration files, Groovy scripts, and
|
2019-09-25 19:29:17 +08:00
|
|
|
|
component classes (typically `@Configuration` classes) to configure an
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`ApplicationContext` for your tests. For example, if you use XML configuration in
|
|
|
|
|
production, you may decide that you want to use `@Configuration` classes to configure
|
|
|
|
|
specific Spring-managed components for your tests, or vice versa.
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Furthermore, some third-party frameworks (such as Spring Boot) provide first-class
|
|
|
|
|
support for loading an `ApplicationContext` from different types of resources
|
|
|
|
|
simultaneously (for example, XML configuration files, Groovy scripts, and
|
|
|
|
|
`@Configuration` classes). The Spring Framework, historically, has not supported this for
|
|
|
|
|
standard deployments. Consequently, most of the `SmartContextLoader` implementations that
|
|
|
|
|
the Spring Framework delivers in the `spring-test` module support only one resource type
|
|
|
|
|
for each test context. However, this does not mean that you cannot use both. One
|
|
|
|
|
exception to the general rule is that the `GenericGroovyXmlContextLoader` and
|
|
|
|
|
`GenericGroovyXmlWebContextLoader` support both XML configuration files and Groovy
|
|
|
|
|
scripts simultaneously. Furthermore, third-party frameworks may choose to support the
|
|
|
|
|
declaration of both `locations` and `classes` through `@ContextConfiguration`, and, with
|
|
|
|
|
the standard testing support in the TestContext framework, you have the following options.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
If you want to use resource locations (for example, XML or Groovy) and `@Configuration`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
classes to configure your tests, you must pick one as the entry point, and that one must
|
|
|
|
|
include or import the other. For example, in XML or Groovy scripts, you can include
|
|
|
|
|
`@Configuration` classes by using component scanning or defining them as normal Spring
|
|
|
|
|
beans, whereas, in a `@Configuration` class, you can use `@ImportResource` to import XML
|
|
|
|
|
configuration files or Groovy scripts. Note that this behavior is semantically equivalent
|
|
|
|
|
to how you configure your application in production: In production configuration, you
|
|
|
|
|
define either a set of XML or Groovy resource locations or a set of `@Configuration`
|
|
|
|
|
classes from which your production `ApplicationContext` is loaded, but you still have the
|
|
|
|
|
freedom to include or import the other type of configuration.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-initializers]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Context Configuration with Context Initializers
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
To configure an `ApplicationContext` for your tests by using context initializers,
|
|
|
|
|
annotate your test class with `@ContextConfiguration` and configure the `initializers`
|
|
|
|
|
attribute with an array that contains references to classes that implement
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`ApplicationContextInitializer`. The declared context initializers are then used to
|
2015-03-03 18:38:01 +08:00
|
|
|
|
initialize the `ConfigurableApplicationContext` that is loaded for your tests. Note that
|
2018-09-18 22:42:09 +08:00
|
|
|
|
the concrete `ConfigurableApplicationContext` type supported by each declared initializer
|
|
|
|
|
must be compatible with the type of `ApplicationContext` created by the
|
|
|
|
|
`SmartContextLoader` in use (typically a `GenericApplicationContext`). Furthermore, the
|
|
|
|
|
order in which the initializers are invoked depends on whether they implement Spring's
|
|
|
|
|
`Ordered` interface or are annotated with Spring's `@Order` annotation or the standard
|
|
|
|
|
`@Priority` annotation. The following example shows how to use initializers:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from TestConfig
|
|
|
|
|
// and initialized by TestAppCtxInitializer
|
2018-08-30 23:29:17 +08:00
|
|
|
|
@ContextConfiguration(
|
2015-03-03 18:38:01 +08:00
|
|
|
|
classes = TestConfig.class,
|
2019-08-30 08:35:53 +08:00
|
|
|
|
initializers = TestAppCtxInitializer.class) // <1>
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specifying configuration by using a configuration class and an initializer.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be loaded from TestConfig
|
|
|
|
|
// and initialized by TestAppCtxInitializer
|
|
|
|
|
@ContextConfiguration(
|
|
|
|
|
classes = [TestConfig::class],
|
|
|
|
|
initializers = [TestAppCtxInitializer::class]) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Specifying configuration by using a configuration class and an initializer.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
You can also omit the declaration of XML configuration files, Groovy scripts, or
|
2019-09-25 19:29:17 +08:00
|
|
|
|
component classes in `@ContextConfiguration` entirely and instead declare only
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`ApplicationContextInitializer` classes, which are then responsible for registering beans
|
2015-03-03 18:38:01 +08:00
|
|
|
|
in the context -- for example, by programmatically loading bean definitions from XML
|
2018-08-30 23:29:17 +08:00
|
|
|
|
files or configuration classes. The following example shows how to do so:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be initialized by EntireAppInitializer
|
|
|
|
|
// which presumably registers beans in the context
|
2018-08-30 23:29:17 +08:00
|
|
|
|
@ContextConfiguration(initializers = EntireAppInitializer.class) <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 21:43:14 +08:00
|
|
|
|
<1> Specifying configuration by using only an initializer.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be initialized by EntireAppInitializer
|
|
|
|
|
// which presumably registers beans in the context
|
|
|
|
|
@ContextConfiguration(initializers = [EntireAppInitializer::class]) // <1>
|
|
|
|
|
class MyTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specifying configuration by using only an initializer.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-inheritance]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Context Configuration Inheritance
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`@ContextConfiguration` supports boolean `inheritLocations` and `inheritInitializers`
|
2019-09-25 19:29:17 +08:00
|
|
|
|
attributes that denote whether resource locations or component classes and context
|
2018-09-18 22:42:09 +08:00
|
|
|
|
initializers declared by superclasses should be inherited. The default value for both
|
|
|
|
|
flags is `true`. This means that a test class inherits the resource locations or
|
2019-09-25 19:29:17 +08:00
|
|
|
|
component classes as well as the context initializers declared by any superclasses.
|
|
|
|
|
Specifically, the resource locations or component classes for a test class are appended
|
2015-03-03 18:38:01 +08:00
|
|
|
|
to the list of resource locations or annotated classes declared by superclasses.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Similarly, the initializers for a given test class are added to the set of initializers
|
|
|
|
|
defined by test superclasses. Thus, subclasses have the option of extending the resource
|
2019-09-25 19:29:17 +08:00
|
|
|
|
locations, component classes, or context initializers.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2015-06-21 22:25:00 +08:00
|
|
|
|
If the `inheritLocations` or `inheritInitializers` attribute in `@ContextConfiguration`
|
2019-09-25 19:29:17 +08:00
|
|
|
|
is set to `false`, the resource locations or component classes and the context
|
2018-08-30 23:29:17 +08:00
|
|
|
|
initializers, respectively, for the test class shadow and effectively replace the
|
2015-03-03 18:38:01 +08:00
|
|
|
|
configuration defined by superclasses.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In the next example, which uses XML resource locations, the `ApplicationContext` for
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ExtendedTest` is loaded from `base-config.xml` and `extended-config.xml`, in that order.
|
|
|
|
|
Beans defined in `extended-config.xml` can, therefore, override (that is, replace) those
|
|
|
|
|
defined in `base-config.xml`. The following example shows how one class can extend
|
|
|
|
|
another and use both its own configuration file and the superclass's configuration file:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from "/base-config.xml"
|
|
|
|
|
// in the root of the classpath
|
2018-08-30 23:29:17 +08:00
|
|
|
|
@ContextConfiguration("/base-config.xml") <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplicationContext will be loaded from "/base-config.xml" and
|
|
|
|
|
// "/extended-config.xml" in the root of the classpath
|
2018-08-30 23:29:17 +08:00
|
|
|
|
@ContextConfiguration("/extended-config.xml") <2>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ExtendedTest extends BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Configuration file defined in the superclass.
|
|
|
|
|
<2> Configuration file defined in the subclass.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be loaded from "/base-config.xml"
|
|
|
|
|
// in the root of the classpath
|
|
|
|
|
@ContextConfiguration("/base-config.xml") // <1>
|
|
|
|
|
open class BaseTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplicationContext will be loaded from "/base-config.xml" and
|
|
|
|
|
// "/extended-config.xml" in the root of the classpath
|
|
|
|
|
@ContextConfiguration("/extended-config.xml") // <2>
|
|
|
|
|
class ExtendedTest : BaseTest() {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Configuration file defined in the superclass.
|
|
|
|
|
<2> Configuration file defined in the subclass.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-09-25 19:29:17 +08:00
|
|
|
|
Similarly, in the next example, which uses component classes, the `ApplicationContext`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
for `ExtendedTest` is loaded from the `BaseConfig` and `ExtendedConfig` classes, in that
|
|
|
|
|
order. Beans defined in `ExtendedConfig` can, therefore, override (that is, replace)
|
|
|
|
|
those defined in `BaseConfig`. The following example shows how one class can extend
|
|
|
|
|
another and use both its own configuration class and the superclass's configuration class:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
// ApplicationContext will be loaded from BaseConfig
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitConfig(BaseConfig.class) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitConfig(ExtendedConfig.class) // <2>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ExtendedTest extends BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Configuration class defined in the superclass.
|
|
|
|
|
<2> Configuration class defined in the subclass.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// ApplicationContext will be loaded from BaseConfig
|
|
|
|
|
@SpringJUnitConfig(BaseConfig::class) // <1>
|
|
|
|
|
open class BaseTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
|
|
|
|
|
@SpringJUnitConfig(ExtendedConfig::class) // <2>
|
|
|
|
|
class ExtendedTest : BaseTest() {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Configuration class defined in the superclass.
|
|
|
|
|
<2> Configuration class defined in the subclass.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In the next example, which uses context initializers, the `ApplicationContext` for
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ExtendedTest` is initialized by using `BaseInitializer` and `ExtendedInitializer`. Note,
|
|
|
|
|
however, that the order in which the initializers are invoked depends on whether they
|
|
|
|
|
implement Spring's `Ordered` interface or are annotated with Spring's `@Order` annotation
|
|
|
|
|
or the standard `@Priority` annotation. The following example shows how one class can
|
|
|
|
|
extend another and use both its own initializer and the superclass's initializer:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
// ApplicationContext will be initialized by BaseInitializer
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitConfig(initializers = BaseInitializer.class) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplicationContext will be initialized by BaseInitializer
|
|
|
|
|
// and ExtendedInitializer
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@SpringJUnitConfig(initializers = ExtendedInitializer.class) // <2>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ExtendedTest extends BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Initializer defined in the superclass.
|
|
|
|
|
<2> Initializer defined in the subclass.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// ApplicationContext will be initialized by BaseInitializer
|
|
|
|
|
@SpringJUnitConfig(initializers = [BaseInitializer::class]) // <1>
|
|
|
|
|
open class BaseTest {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplicationContext will be initialized by BaseInitializer
|
|
|
|
|
// and ExtendedInitializer
|
|
|
|
|
@SpringJUnitConfig(initializers = [ExtendedInitializer::class]) // <2>
|
|
|
|
|
class ExtendedTest : BaseTest() {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Initializer defined in the superclass.
|
|
|
|
|
<2> Initializer defined in the subclass.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-env-profiles]]
|
2018-09-18 21:43:14 +08:00
|
|
|
|
===== Context Configuration with Environment Profiles
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2019-09-26 21:02:30 +08:00
|
|
|
|
The Spring Framework has first-class support for the notion of environments and profiles
|
|
|
|
|
(AKA "bean definition profiles"), and integration tests can be configured to activate
|
|
|
|
|
particular bean definition profiles for various testing scenarios. This is achieved by
|
|
|
|
|
annotating a test class with the `@ActiveProfiles` annotation and supplying a list of
|
|
|
|
|
profiles that should be activated when loading the `ApplicationContext` for the test.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
NOTE: You can use `@ActiveProfiles` with any implementation of the `SmartContextLoader`
|
|
|
|
|
SPI, but `@ActiveProfiles` is not supported with implementations of the older
|
|
|
|
|
`ContextLoader` SPI.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Consider two examples with XML configuration and `@Configuration` classes:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
<!-- app-config.xml -->
|
|
|
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
|
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
|
|
|
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
|
|
|
|
xmlns:jee="http://www.springframework.org/schema/jee"
|
|
|
|
|
xsi:schemaLocation="...">
|
|
|
|
|
|
|
|
|
|
<bean id="transferService"
|
|
|
|
|
class="com.bank.service.internal.DefaultTransferService">
|
|
|
|
|
<constructor-arg ref="accountRepository"/>
|
|
|
|
|
<constructor-arg ref="feePolicy"/>
|
|
|
|
|
</bean>
|
|
|
|
|
|
|
|
|
|
<bean id="accountRepository"
|
|
|
|
|
class="com.bank.repository.internal.JdbcAccountRepository">
|
|
|
|
|
<constructor-arg ref="dataSource"/>
|
|
|
|
|
</bean>
|
|
|
|
|
|
|
|
|
|
<bean id="feePolicy"
|
|
|
|
|
class="com.bank.service.internal.ZeroFeePolicy"/>
|
|
|
|
|
|
|
|
|
|
<beans profile="dev">
|
|
|
|
|
<jdbc:embedded-database id="dataSource">
|
|
|
|
|
<jdbc:script
|
|
|
|
|
location="classpath:com/bank/config/sql/schema.sql"/>
|
|
|
|
|
<jdbc:script
|
|
|
|
|
location="classpath:com/bank/config/sql/test-data.sql"/>
|
|
|
|
|
</jdbc:embedded-database>
|
|
|
|
|
</beans>
|
|
|
|
|
|
|
|
|
|
<beans profile="production">
|
|
|
|
|
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
|
|
|
|
|
</beans>
|
|
|
|
|
|
|
|
|
|
<beans profile="default">
|
|
|
|
|
<jdbc:embedded-database id="dataSource">
|
|
|
|
|
<jdbc:script
|
|
|
|
|
location="classpath:com/bank/config/sql/schema.sql"/>
|
|
|
|
|
</jdbc:embedded-database>
|
|
|
|
|
</beans>
|
|
|
|
|
|
|
|
|
|
</beans>
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ApplicationContext will be loaded from "classpath:/app-config.xml"
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration("/app-config.xml")
|
|
|
|
|
@ActiveProfiles("dev")
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class TransferServiceTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
TransferService transferService;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void testTransferService() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// test the transferService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// ApplicationContext will be loaded from "classpath:/app-config.xml"
|
|
|
|
|
@ContextConfiguration("/app-config.xml")
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
class TransferServiceTest {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var transferService: TransferService
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testTransferService() {
|
|
|
|
|
// test the transferService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
When `TransferServiceTest` is run, its `ApplicationContext` is loaded from the
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`app-config.xml` configuration file in the root of the classpath. If you inspect
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`app-config.xml`, you can see that the `accountRepository` bean has a dependency on a
|
|
|
|
|
`dataSource` bean. However, `dataSource` is not defined as a top-level bean. Instead,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`dataSource` is defined three times: in the `production` profile, in the `dev` profile,
|
|
|
|
|
and in the `default` profile.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
By annotating `TransferServiceTest` with `@ActiveProfiles("dev")`, we instruct the Spring
|
2015-03-03 18:38:01 +08:00
|
|
|
|
TestContext Framework to load the `ApplicationContext` with the active profiles set to
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`{"dev"}`. As a result, an embedded database is created and populated with test data, and
|
|
|
|
|
the `accountRepository` bean is wired with a reference to the development `DataSource`.
|
|
|
|
|
That is likely what we want in an integration test.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
It is sometimes useful to assign beans to a `default` profile. Beans within the default
|
|
|
|
|
profile are included only when no other profile is specifically activated. You can use
|
|
|
|
|
this to define "`fallback`" beans to be used in the application's default state. For
|
|
|
|
|
example, you may explicitly provide a data source for `dev` and `production` profiles,
|
|
|
|
|
but define an in-memory data source as a default when neither of these is active.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
The following code listings demonstrate how to implement the same configuration and
|
2018-08-30 23:29:17 +08:00
|
|
|
|
integration test with `@Configuration` classes instead of XML:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Configuration
|
|
|
|
|
@Profile("dev")
|
|
|
|
|
public class StandaloneDataConfig {
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
public DataSource dataSource() {
|
|
|
|
|
return new EmbeddedDatabaseBuilder()
|
|
|
|
|
.setType(EmbeddedDatabaseType.HSQL)
|
|
|
|
|
.addScript("classpath:com/bank/config/sql/schema.sql")
|
|
|
|
|
.addScript("classpath:com/bank/config/sql/test-data.sql")
|
|
|
|
|
.build();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Configuration
|
|
|
|
|
@Profile("dev")
|
|
|
|
|
class StandaloneDataConfig {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Bean
|
|
|
|
|
fun dataSource(): DataSource {
|
|
|
|
|
return EmbeddedDatabaseBuilder()
|
|
|
|
|
.setType(EmbeddedDatabaseType.HSQL)
|
|
|
|
|
.addScript("classpath:com/bank/config/sql/schema.sql")
|
|
|
|
|
.addScript("classpath:com/bank/config/sql/test-data.sql")
|
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Configuration
|
|
|
|
|
@Profile("production")
|
|
|
|
|
public class JndiDataConfig {
|
|
|
|
|
|
|
|
|
|
@Bean(destroyMethod="")
|
|
|
|
|
public DataSource dataSource() throws Exception {
|
|
|
|
|
Context ctx = new InitialContext();
|
|
|
|
|
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Configuration
|
|
|
|
|
@Profile("production")
|
|
|
|
|
class JndiDataConfig {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Bean(destroyMethod = "")
|
|
|
|
|
fun dataSource(): DataSource {
|
|
|
|
|
val ctx = InitialContext()
|
|
|
|
|
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Configuration
|
|
|
|
|
@Profile("default")
|
|
|
|
|
public class DefaultDataConfig {
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
public DataSource dataSource() {
|
|
|
|
|
return new EmbeddedDatabaseBuilder()
|
|
|
|
|
.setType(EmbeddedDatabaseType.HSQL)
|
|
|
|
|
.addScript("classpath:com/bank/config/sql/schema.sql")
|
|
|
|
|
.build();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Configuration
|
|
|
|
|
@Profile("default")
|
|
|
|
|
class DefaultDataConfig {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Bean
|
|
|
|
|
fun dataSource(): DataSource {
|
|
|
|
|
return EmbeddedDatabaseBuilder()
|
|
|
|
|
.setType(EmbeddedDatabaseType.HSQL)
|
|
|
|
|
.addScript("classpath:com/bank/config/sql/schema.sql")
|
|
|
|
|
.build()
|
2019-08-31 21:07:12 +08:00
|
|
|
|
}
|
2019-08-30 08:35:53 +08:00
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Configuration
|
|
|
|
|
public class TransferServiceConfig {
|
|
|
|
|
|
|
|
|
|
@Autowired DataSource dataSource;
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
public TransferService transferService() {
|
|
|
|
|
return new DefaultTransferService(accountRepository(), feePolicy());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
public AccountRepository accountRepository() {
|
|
|
|
|
return new JdbcAccountRepository(dataSource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
public FeePolicy feePolicy() {
|
|
|
|
|
return new ZeroFeePolicy();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Configuration
|
|
|
|
|
class TransferServiceConfig {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var dataSource: DataSource
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
fun transferService(): TransferService {
|
|
|
|
|
return DefaultTransferService(accountRepository(), feePolicy())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
fun accountRepository(): AccountRepository {
|
|
|
|
|
return JdbcAccountRepository(dataSource)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
fun feePolicy(): FeePolicy {
|
|
|
|
|
return ZeroFeePolicy()
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitConfig({
|
2015-03-03 18:38:01 +08:00
|
|
|
|
TransferServiceConfig.class,
|
|
|
|
|
StandaloneDataConfig.class,
|
|
|
|
|
JndiDataConfig.class,
|
|
|
|
|
DefaultDataConfig.class})
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ActiveProfiles("dev")
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class TransferServiceTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
TransferService transferService;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void testTransferService() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// test the transferService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(
|
|
|
|
|
TransferServiceConfig::class,
|
|
|
|
|
StandaloneDataConfig::class,
|
|
|
|
|
JndiDataConfig::class,
|
|
|
|
|
DefaultDataConfig::class)
|
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
class TransferServiceTest {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var transferService: TransferService
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testTransferService() {
|
|
|
|
|
// test the transferService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
In this variation, we have split the XML configuration into four independent
|
|
|
|
|
`@Configuration` classes:
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `TransferServiceConfig`: Acquires a `dataSource` through dependency injection by using
|
|
|
|
|
`@Autowired`.
|
|
|
|
|
* `StandaloneDataConfig`: Defines a `dataSource` for an embedded database suitable for
|
|
|
|
|
developer tests.
|
|
|
|
|
* `JndiDataConfig`: Defines a `dataSource` that is retrieved from JNDI in a production
|
|
|
|
|
environment.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* `DefaultDataConfig`: Defines a `dataSource` for a default embedded database, in case no
|
|
|
|
|
profile is active.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
As with the XML-based configuration example, we still annotate `TransferServiceTest` with
|
|
|
|
|
`@ActiveProfiles("dev")`, but this time we specify all four configuration classes by
|
|
|
|
|
using the `@ContextConfiguration` annotation. The body of the test class itself remains
|
2015-03-03 18:38:01 +08:00
|
|
|
|
completely unchanged.
|
|
|
|
|
|
|
|
|
|
It is often the case that a single set of profiles is used across multiple test classes
|
|
|
|
|
within a given project. Thus, to avoid duplicate declarations of the `@ActiveProfiles`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
annotation, you can declare `@ActiveProfiles` once on a base class, and subclasses
|
|
|
|
|
automatically inherit the `@ActiveProfiles` configuration from the base class. In the
|
|
|
|
|
following example, the declaration of `@ActiveProfiles` (as well as other annotations)
|
|
|
|
|
has been moved to an abstract superclass, `AbstractIntegrationTest`:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitConfig({
|
2015-03-03 18:38:01 +08:00
|
|
|
|
TransferServiceConfig.class,
|
|
|
|
|
StandaloneDataConfig.class,
|
|
|
|
|
JndiDataConfig.class,
|
|
|
|
|
DefaultDataConfig.class})
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ActiveProfiles("dev")
|
2019-08-21 21:54:28 +08:00
|
|
|
|
abstract class AbstractIntegrationTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(
|
2019-08-31 20:47:49 +08:00
|
|
|
|
TransferServiceConfig::class,
|
|
|
|
|
StandaloneDataConfig::class,
|
|
|
|
|
JndiDataConfig::class,
|
|
|
|
|
DefaultDataConfig::class)
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ActiveProfiles("dev")
|
|
|
|
|
abstract class AbstractIntegrationTest {
|
|
|
|
|
}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// "dev" profile inherited from superclass
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class TransferServiceTest extends AbstractIntegrationTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
TransferService transferService;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void testTransferService() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// test the transferService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// "dev" profile inherited from superclass
|
|
|
|
|
class TransferServiceTest : AbstractIntegrationTest() {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var transferService: TransferService
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testTransferService() {
|
|
|
|
|
// test the transferService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
`@ActiveProfiles` also supports an `inheritProfiles` attribute that can be used to
|
2018-08-30 23:29:17 +08:00
|
|
|
|
disable the inheritance of active profiles, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
// "dev" profile overridden with "production"
|
|
|
|
|
@ActiveProfiles(profiles = "production", inheritProfiles = false)
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ProductionTransferServiceTest extends AbstractIntegrationTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// test body
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// "dev" profile overridden with "production"
|
|
|
|
|
@ActiveProfiles("production", inheritProfiles = false)
|
|
|
|
|
class ProductionTransferServiceTest : AbstractIntegrationTest() {
|
|
|
|
|
// test body
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-env-profiles-ActiveProfilesResolver]]
|
|
|
|
|
Furthermore, it is sometimes necessary to resolve active profiles for tests
|
2018-08-30 23:29:17 +08:00
|
|
|
|
programmatically instead of declaratively -- for example, based on:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* The current operating system.
|
|
|
|
|
* Whether tests are being executed on a continuous integration build server.
|
|
|
|
|
* The presence of certain environment variables.
|
|
|
|
|
* The presence of custom class-level annotations.
|
|
|
|
|
* Other concerns.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
To resolve active bean definition profiles programmatically, you can implement
|
|
|
|
|
a custom `ActiveProfilesResolver` and register it by using the `resolver`
|
|
|
|
|
attribute of `@ActiveProfiles`. For further information, see the corresponding
|
|
|
|
|
{api-spring-framework}/test/context/ActiveProfilesResolver.html[javadoc].
|
|
|
|
|
The following example demonstrates how to implement and register a custom
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`OperatingSystemActiveProfilesResolver`:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
// "dev" profile overridden programmatically via a custom resolver
|
|
|
|
|
@ActiveProfiles(
|
2019-08-31 20:47:49 +08:00
|
|
|
|
resolver = OperatingSystemActiveProfilesResolver.class,
|
|
|
|
|
inheritProfiles = false)
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class TransferServiceTest extends AbstractIntegrationTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// test body
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// "dev" profile overridden programmatically via a custom resolver
|
|
|
|
|
@ActiveProfiles(
|
|
|
|
|
resolver = OperatingSystemActiveProfilesResolver::class,
|
|
|
|
|
inheritProfiles = false)
|
|
|
|
|
class TransferServiceTest : AbstractIntegrationTest() {
|
|
|
|
|
// test body
|
|
|
|
|
}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
|
|
|
|
|
|
|
|
|
|
@Override
|
2019-08-21 21:54:28 +08:00
|
|
|
|
public String[] resolve(Class<?> testClass) {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
String profile = ...;
|
|
|
|
|
// determine the value of profile based on the operating system
|
|
|
|
|
return new String[] {profile};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {
|
|
|
|
|
|
|
|
|
|
override fun resolve(testClass: Class<*>): Array<String> {
|
|
|
|
|
val profile: String = ...
|
|
|
|
|
// determine the value of profile based on the operating system
|
|
|
|
|
return arrayOf(profile)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-property-sources]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Context Configuration with Test Property Sources
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-09-26 21:02:30 +08:00
|
|
|
|
The Spring Framework has first-class support for the notion of an environment with a
|
|
|
|
|
hierarchy of property sources, and you can configure integration tests with test-specific
|
|
|
|
|
property sources. In contrast to the `@PropertySource` annotation used on
|
|
|
|
|
`@Configuration` classes, you can declare the `@TestPropertySource` annotation on a test
|
|
|
|
|
class to declare resource locations for test properties files or inlined properties.
|
|
|
|
|
These test property sources are added to the set of `PropertySources` in the
|
|
|
|
|
`Environment` for the `ApplicationContext` loaded for the annotated integration test.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[NOTE]
|
2015-03-06 23:57:32 +08:00
|
|
|
|
====
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can use `@TestPropertySource` with any implementation of the `SmartContextLoader`
|
2015-03-03 18:38:01 +08:00
|
|
|
|
SPI, but `@TestPropertySource` is not supported with implementations of the older
|
|
|
|
|
`ContextLoader` SPI.
|
|
|
|
|
|
|
|
|
|
Implementations of `SmartContextLoader` gain access to merged test property source values
|
2018-08-30 23:29:17 +08:00
|
|
|
|
through the `getPropertySourceLocations()` and `getPropertySourceProperties()` methods in
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`MergedContextConfiguration`.
|
2015-03-06 23:57:32 +08:00
|
|
|
|
====
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Declaring Test Property Sources
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can configure test properties files by using the `locations` or `value` attribute of
|
|
|
|
|
`@TestPropertySource`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Both traditional and XML-based properties file formats are supported -- for example,
|
|
|
|
|
`"classpath:/com/example/test.properties"` or `"file:///path/to/file.xml"`.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Each path is interpreted as a Spring `Resource`. A plain path (for example,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`"test.properties"`) is treated as a classpath resource that is relative to the package
|
|
|
|
|
in which the test class is defined. A path starting with a slash is treated as an
|
|
|
|
|
absolute classpath resource (for example: `"/org/example/test.xml"`). A path that
|
|
|
|
|
references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is
|
|
|
|
|
loaded by using the specified resource protocol. Resource location wildcards (such as
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`**/*.properties`) are not permitted: Each location must evaluate to exactly one
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`.properties` or `.xml` resource.
|
|
|
|
|
|
2018-09-18 21:38:06 +08:00
|
|
|
|
The following example uses a test properties file:
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestPropertySource("/test.properties") // <1>
|
|
|
|
|
class MyIntegrationTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Specifying a properties file with an absolute path.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@TestPropertySource("/test.properties") // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyIntegrationTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Specifying a properties file with an absolute path.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can configure inlined properties in the form of key-value pairs by using the
|
|
|
|
|
`properties` attribute of `@TestPropertySource`, as shown in the next example. All
|
|
|
|
|
key-value pairs are added to the enclosing `Environment` as a single test
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`PropertySource` with the highest precedence.
|
|
|
|
|
|
|
|
|
|
The supported syntax for key-value pairs is the same as the syntax defined for entries in
|
|
|
|
|
a Java properties file:
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `key=value`
|
|
|
|
|
* `key:value`
|
|
|
|
|
* `key value`
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example sets two inlined properties:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) // <1>
|
|
|
|
|
class MyIntegrationTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Setting two properties by using two variations of the key-value syntax.
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1>
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyIntegrationTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
<1> Setting two properties by using two variations of the key-value syntax.
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2019-07-28 05:02:43 +08:00
|
|
|
|
[NOTE]
|
|
|
|
|
====
|
|
|
|
|
As of Spring Framework 5.2, `@TestPropertySource` can be used as _repeatable annotation_.
|
|
|
|
|
That means that you can have multiple declarations of `@TestPropertySource` on a single
|
|
|
|
|
test class, with the `locations` and `properties` from later `@TestPropertySource`
|
|
|
|
|
annotations overriding those from previous `@TestPropertySource` annotations.
|
|
|
|
|
|
|
|
|
|
In addition, you may declare multiple composed annotations on a test class that are each
|
|
|
|
|
meta-annotated with `@TestPropertySource`, and all of those `@TestPropertySource`
|
|
|
|
|
declarations will contribute to your test property sources.
|
|
|
|
|
|
|
|
|
|
Directly present `@TestPropertySource` annotations always take precedence over
|
|
|
|
|
meta-present `@TestPropertySource` annotations. In other words, `locations` and
|
|
|
|
|
`properties` from a directly present `@TestPropertySource` annotation will override the
|
|
|
|
|
`locations` and `properties` from a `@TestPropertySource` annotation used as a
|
|
|
|
|
meta-annotation.
|
|
|
|
|
====
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Default Properties File Detection
|
|
|
|
|
|
|
|
|
|
If `@TestPropertySource` is declared as an empty annotation (that is, without explicit
|
2018-09-18 22:42:09 +08:00
|
|
|
|
values for the `locations` or `properties` attributes), an attempt is made to detect a
|
|
|
|
|
default properties file relative to the class that declared the annotation. For example,
|
|
|
|
|
if the annotated test class is `com.example.MyTest`, the corresponding default properties
|
|
|
|
|
file is `classpath:com/example/MyTest.properties`. If the default cannot be detected, an
|
|
|
|
|
`IllegalStateException` is thrown.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Precedence
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2020-04-01 23:56:58 +08:00
|
|
|
|
Test properties have higher precedence than those defined in the operating system's
|
|
|
|
|
environment, Java system properties, or property sources added by the application
|
|
|
|
|
declaratively by using `@PropertySource` or programmatically. Thus, test properties can
|
|
|
|
|
be used to selectively override properties loaded from system and application property
|
|
|
|
|
sources. Furthermore, inlined properties have higher precedence than properties loaded
|
|
|
|
|
from resource locations. Note, however, that properties registered via
|
|
|
|
|
<<testcontext-ctx-management-dynamic-property-sources, `@DynamicPropertySource`>> have
|
|
|
|
|
higher precedence than those loaded via `@TestPropertySource`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In the next example, the `timezone` and `port` properties and any properties defined in
|
|
|
|
|
`"/test.properties"` override any properties of the same name that are defined in system
|
|
|
|
|
and application property sources. Furthermore, if the `"/test.properties"` file defines
|
|
|
|
|
entries for the `timezone` and `port` properties those are overridden by the inlined
|
|
|
|
|
properties declared by using the `properties` attribute. The following example shows how
|
|
|
|
|
to specify properties both in a file and inline:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestPropertySource(
|
|
|
|
|
locations = "/test.properties",
|
|
|
|
|
properties = {"timezone = GMT", "port: 4242"}
|
|
|
|
|
)
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyIntegrationTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
@TestPropertySource("/test.properties",
|
|
|
|
|
properties = ["timezone = GMT", "port: 4242"]
|
|
|
|
|
)
|
|
|
|
|
class MyIntegrationTests {
|
|
|
|
|
// class body...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Inheriting and Overriding Test Property Sources
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
`@TestPropertySource` supports boolean `inheritLocations` and `inheritProperties`
|
|
|
|
|
attributes that denote whether resource locations for properties files and inlined
|
2018-09-18 22:42:09 +08:00
|
|
|
|
properties declared by superclasses should be inherited. The default value for both flags
|
|
|
|
|
is `true`. This means that a test class inherits the locations and inlined properties
|
|
|
|
|
declared by any superclasses. Specifically, the locations and inlined properties for a
|
|
|
|
|
test class are appended to the locations and inlined properties declared by superclasses.
|
|
|
|
|
Thus, subclasses have the option of extending the locations and inlined properties. Note
|
|
|
|
|
that properties that appear later shadow (that is, override) properties of the same name
|
|
|
|
|
that appear earlier. In addition, the aforementioned precedence rules apply for inherited
|
|
|
|
|
test property sources as well.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If the `inheritLocations` or `inheritProperties` attribute in `@TestPropertySource` is
|
|
|
|
|
set to `false`, the locations or inlined properties, respectively, for the test class
|
|
|
|
|
shadow and effectively replace the configuration defined by superclasses.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In the next example, the `ApplicationContext` for `BaseTest` is loaded by using only the
|
|
|
|
|
`base.properties` file as a test property source. In contrast, the `ApplicationContext`
|
|
|
|
|
for `ExtendedTest` is loaded by using the `base.properties` and `extended.properties`
|
|
|
|
|
files as test property source locations. The following example shows how to define
|
|
|
|
|
properties in both a subclass and its superclass by using `properties` files:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@TestPropertySource("base.properties")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@ContextConfiguration
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@TestPropertySource("extended.properties")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@ContextConfiguration
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ExtendedTest extends BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@TestPropertySource("base.properties")
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
open class BaseTest {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@TestPropertySource("extended.properties")
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
class ExtendedTest : BaseTest() {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
In the next example, the `ApplicationContext` for `BaseTest` is loaded by using only the
|
|
|
|
|
inlined `key1` property. In contrast, the `ApplicationContext` for `ExtendedTest` is
|
|
|
|
|
loaded by using the inlined `key1` and `key2` properties. The following example shows how
|
|
|
|
|
to define properties in both a subclass and its superclass by using inline properties:
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@TestPropertySource(properties = "key1 = value1")
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
class BaseTest {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@TestPropertySource(properties = "key2 = value2")
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
class ExtendedTest extends BaseTest {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@TestPropertySource(properties = ["key1 = value1"])
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
open class BaseTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@TestPropertySource(properties = ["key2 = value2"])
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
class ExtendedTest : BaseTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2020-03-24 03:43:38 +08:00
|
|
|
|
[[testcontext-ctx-management-dynamic-property-sources]]
|
|
|
|
|
===== Context Configuration with Dynamic Property Sources
|
|
|
|
|
|
|
|
|
|
As of Spring Framework 5.2.5, the TestContext framework provides support for _dynamic_
|
2020-04-01 23:56:58 +08:00
|
|
|
|
properties via the `@DynamicPropertySource` annotation. This annotation can be used in
|
|
|
|
|
integration tests that need to add properties with dynamic values to the set of
|
2020-03-24 03:43:38 +08:00
|
|
|
|
`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the
|
|
|
|
|
integration test.
|
|
|
|
|
|
|
|
|
|
NOTE: The `@DynamicPropertySource` annotation and its supporting infrastructure were
|
|
|
|
|
originally designed to allow properties from
|
|
|
|
|
https://www.testcontainers.org/[Testcontainers] based tests to be exposed easily to
|
|
|
|
|
Spring integration tests. However, this feature may also be used with any form of
|
|
|
|
|
external resource whose lifecycle is maintained outside the test's `ApplicationContext`.
|
|
|
|
|
|
|
|
|
|
In contrast to the <<testcontext-ctx-management-property-sources,`@TestPropertySource`>>
|
|
|
|
|
annotation that is applied at the class level, `@DynamicPropertySource` must be applied
|
|
|
|
|
to a `static` method that accepts a single `DynamicPropertyRegistry` argument which is
|
|
|
|
|
used to add _name-value_ pairs to the `Environment`. Values are dynamic and provided via
|
|
|
|
|
a `Supplier` which is only invoked when the property is resolved. Typically, method
|
|
|
|
|
references are used to supply values, as can be seen in the following example which uses
|
|
|
|
|
the Testcontainers project to manage a Redis container outside of the Spring
|
|
|
|
|
`ApplicationContext`. The IP address and port of the managed Redis container are made
|
|
|
|
|
available to components within the test's `ApplicationContext` via the `redis.host` and
|
2020-04-01 23:56:58 +08:00
|
|
|
|
`redis.port` properties. These properties can be accessed via Spring's `Environment`
|
|
|
|
|
abstraction or injected directly into Spring-managed components – for example, via
|
|
|
|
|
`@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
|
2020-03-24 03:43:38 +08:00
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(/* ... */)
|
|
|
|
|
@Testcontainers
|
|
|
|
|
class ExampleIntegrationTests {
|
|
|
|
|
|
|
|
|
|
@Container
|
|
|
|
|
static RedisContainer redis = new RedisContainer();
|
|
|
|
|
|
|
|
|
|
@DynamicPropertySource
|
|
|
|
|
static void redisProperties(DynamicPropertyRegistry registry) {
|
|
|
|
|
registry.add("redis.host", redis::getContainerIpAddress);
|
|
|
|
|
registry.add("redis.port", redis::getMappedPort);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tests ...
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
2020-03-24 16:59:34 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(/* ... */)
|
|
|
|
|
@Testcontainers
|
|
|
|
|
class ExampleIntegrationTests {
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
|
|
@Container
|
|
|
|
|
@JvmStatic
|
|
|
|
|
val redis: RedisContainer = RedisContainer()
|
|
|
|
|
|
|
|
|
|
@DynamicPropertySource
|
|
|
|
|
@JvmStatic
|
|
|
|
|
fun redisProperties(registry: DynamicPropertyRegistry) {
|
|
|
|
|
registry.add("redis.host", redis::getContainerIpAddress)
|
|
|
|
|
registry.add("redis.port", redis::getMappedPort)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tests ...
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
2020-03-24 03:43:38 +08:00
|
|
|
|
|
2020-04-01 23:56:58 +08:00
|
|
|
|
====== Precedence
|
|
|
|
|
|
|
|
|
|
Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,
|
|
|
|
|
the operating system's environment, Java system properties, or property sources added by
|
|
|
|
|
the application declaratively by using `@PropertySource` or programmatically. Thus,
|
|
|
|
|
dynamic properties can be used to selectively override properties loaded via
|
|
|
|
|
`@TestPropertySource`, system property sources, and application property sources.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-ctx-management-web]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Loading a `WebApplicationContext`
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
To instruct the TestContext framework to load a `WebApplicationContext` instead of a
|
|
|
|
|
standard `ApplicationContext`, you can annotate the respective test class with
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`@WebAppConfiguration`.
|
|
|
|
|
|
|
|
|
|
The presence of `@WebAppConfiguration` on your test class instructs the TestContext
|
|
|
|
|
framework (TCF) that a `WebApplicationContext` (WAC) should be loaded for your
|
2018-08-30 23:29:17 +08:00
|
|
|
|
integration tests. In the background, the TCF makes sure that a `MockServletContext` is
|
|
|
|
|
created and supplied to your test's WAC. By default, the base resource path for your
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`MockServletContext` is set to `src/main/webapp`. This is interpreted as a path relative
|
|
|
|
|
to the root of your JVM (normally the path to your project). If you are familiar with the
|
|
|
|
|
directory structure of a web application in a Maven project, you know that
|
|
|
|
|
`src/main/webapp` is the default location for the root of your WAR. If you need to
|
|
|
|
|
override this default, you can provide an alternate path to the `@WebAppConfiguration`
|
|
|
|
|
annotation (for example, `@WebAppConfiguration("src/test/webapp")`). If you wish to
|
|
|
|
|
reference a base resource path from the classpath instead of the file system, you can use
|
|
|
|
|
Spring's `classpath:` prefix.
|
|
|
|
|
|
|
|
|
|
Note that Spring's testing support for `WebApplicationContext` implementations is on par
|
|
|
|
|
with its support for standard `ApplicationContext` implementations. When testing with a
|
|
|
|
|
`WebApplicationContext`, you are free to declare XML configuration files, Groovy scripts,
|
|
|
|
|
or `@Configuration` classes by using `@ContextConfiguration`. You are also free to use
|
|
|
|
|
any other test annotations, such as `@ActiveProfiles`, `@TestExecutionListeners`, `@Sql`,
|
|
|
|
|
`@Rollback`, and others.
|
|
|
|
|
|
|
|
|
|
The remaining examples in this section show some of the various configuration options for
|
|
|
|
|
loading a `WebApplicationContext`. The following example shows the TestContext
|
|
|
|
|
framework's support for convention over configuration:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Conventions
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// defaults to "file:src/main/webapp"
|
|
|
|
|
@WebAppConfiguration
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
// detects "WacTests-context.xml" in the same package
|
|
|
|
|
// or static nested @Configuration classes
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@ContextConfiguration
|
2019-08-30 08:35:53 +08:00
|
|
|
|
class WacTests {
|
|
|
|
|
//...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
// defaults to "file:src/main/webapp"
|
|
|
|
|
@WebAppConfiguration
|
|
|
|
|
|
|
|
|
|
// detects "WacTests-context.xml" in the same package
|
|
|
|
|
// or static nested @Configuration classes
|
|
|
|
|
@ContextConfiguration
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class WacTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
//...
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If you annotate a test class with `@WebAppConfiguration` without specifying a resource
|
|
|
|
|
base path, the resource path effectively defaults to `file:src/main/webapp`. Similarly,
|
2019-09-25 19:29:17 +08:00
|
|
|
|
if you declare `@ContextConfiguration` without specifying resource `locations`, component
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`classes`, or context `initializers`, Spring tries to detect the presence of your
|
|
|
|
|
configuration by using conventions (that is, `WacTests-context.xml` in the same package
|
|
|
|
|
as the `WacTests` class or static nested `@Configuration` classes).
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
The following example shows how to explicitly declare a resource base path with
|
|
|
|
|
`@WebAppConfiguration` and an XML resource location with `@ContextConfiguration`:
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
.Default resource semantics
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// file system resource
|
|
|
|
|
@WebAppConfiguration("webapp")
|
|
|
|
|
|
|
|
|
|
// classpath resource
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration("/spring/test-servlet-config.xml")
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class WacTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
//...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
|
|
|
|
|
// file system resource
|
|
|
|
|
@WebAppConfiguration("webapp")
|
|
|
|
|
|
|
|
|
|
// classpath resource
|
|
|
|
|
@ContextConfiguration("/spring/test-servlet-config.xml")
|
|
|
|
|
class WacTests {
|
|
|
|
|
//...
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
|
|
|
|
The important thing to note here is the different semantics for paths with these two
|
|
|
|
|
annotations. By default, `@WebAppConfiguration` resource paths are file system based,
|
|
|
|
|
whereas `@ContextConfiguration` resource locations are classpath based.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example shows that we can override the default resource semantics for both
|
|
|
|
|
annotations by specifying a Spring resource prefix:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Explicit resource semantics
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// classpath resource
|
|
|
|
|
@WebAppConfiguration("classpath:test-web-resources")
|
|
|
|
|
|
|
|
|
|
// file system resource
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class WacTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
//...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
|
|
|
|
|
// classpath resource
|
|
|
|
|
@WebAppConfiguration("classpath:test-web-resources")
|
|
|
|
|
|
|
|
|
|
// file system resource
|
|
|
|
|
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
|
|
|
|
|
class WacTests {
|
|
|
|
|
//...
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Contrast the comments in this example with the previous example.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.[[testcontext-ctx-management-web-mocks]]Working with Web Mocks
|
|
|
|
|
--
|
2019-09-26 21:02:30 +08:00
|
|
|
|
To provide comprehensive web testing support, the TestContext framework has a
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`ServletTestExecutionListener` that is enabled by default. When testing against a
|
2019-03-05 20:08:34 +08:00
|
|
|
|
`WebApplicationContext`, this <<testcontext-key-abstractions, `TestExecutionListener`>>
|
2018-09-18 22:42:09 +08:00
|
|
|
|
sets up default thread-local state by using Spring Web's `RequestContextHolder` before
|
|
|
|
|
each test method and creates a `MockHttpServletRequest`, a `MockHttpServletResponse`, and
|
|
|
|
|
a `ServletWebRequest` based on the base resource path configured with
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`@WebAppConfiguration`. `ServletTestExecutionListener` also ensures that the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`MockHttpServletResponse` and `ServletWebRequest` can be injected into the test instance,
|
|
|
|
|
and, once the test is complete, it cleans up thread-local state.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Once you have a `WebApplicationContext` loaded for your test, you might find that you
|
2015-03-03 18:38:01 +08:00
|
|
|
|
need to interact with the web mocks -- for example, to set up your test fixture or to
|
2018-09-18 22:42:09 +08:00
|
|
|
|
perform assertions after invoking your web component. The following example shows which
|
|
|
|
|
mocks can be autowired into your test instance. Note that the `WebApplicationContext` and
|
|
|
|
|
`MockServletContext` are both cached across the test suite, whereas the other mocks are
|
|
|
|
|
managed per test method by the `ServletTestExecutionListener`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Injecting mocks
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitWebConfig
|
|
|
|
|
class WacTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
WebApplicationContext wac; // cached
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
MockServletContext servletContext; // cached
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
MockHttpSession session;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
MockHttpServletRequest request;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
MockHttpServletResponse response;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
ServletWebRequest webRequest;
|
|
|
|
|
|
|
|
|
|
//...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitWebConfig
|
|
|
|
|
class WacTests {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var wac: WebApplicationContext // cached
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var servletContext: MockServletContext // cached
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var session: MockHttpSession
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var request: MockHttpServletRequest
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var response: MockHttpServletResponse
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var webRequest: ServletWebRequest
|
|
|
|
|
|
|
|
|
|
//...
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
--
|
|
|
|
|
|
|
|
|
|
[[testcontext-ctx-management-caching]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Context Caching
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Once the TestContext framework loads an `ApplicationContext` (or `WebApplicationContext`)
|
2018-09-18 22:42:09 +08:00
|
|
|
|
for a test, that context is cached and reused for all subsequent tests that declare the
|
|
|
|
|
same unique context configuration within the same test suite. To understand how caching
|
|
|
|
|
works, it is important to understand what is meant by "`unique`" and "`test suite.`"
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
An `ApplicationContext` can be uniquely identified by the combination of configuration
|
|
|
|
|
parameters that is used to load it. Consequently, the unique combination of configuration
|
|
|
|
|
parameters is used to generate a key under which the context is cached. The TestContext
|
|
|
|
|
framework uses the following configuration parameters to build the context cache key:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `locations` (from `@ContextConfiguration`)
|
|
|
|
|
* `classes` (from `@ContextConfiguration`)
|
|
|
|
|
* `contextInitializerClasses` (from `@ContextConfiguration`)
|
2020-04-01 23:56:58 +08:00
|
|
|
|
* `contextCustomizers` (from `ContextCustomizerFactory`) – this includes
|
|
|
|
|
`@DynamicPropertySource` methods as well as various features from Spring Boot's
|
|
|
|
|
testing support such as `@MockBean` and `@SpyBean`.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* `contextLoader` (from `@ContextConfiguration`)
|
|
|
|
|
* `parent` (from `@ContextHierarchy`)
|
|
|
|
|
* `activeProfiles` (from `@ActiveProfiles`)
|
|
|
|
|
* `propertySourceLocations` (from `@TestPropertySource`)
|
|
|
|
|
* `propertySourceProperties` (from `@TestPropertySource`)
|
|
|
|
|
* `resourceBasePath` (from `@WebAppConfiguration`)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
For example, if `TestClassA` specifies `{"app-config.xml", "test-config.xml"}` for the
|
|
|
|
|
`locations` (or `value`) attribute of `@ContextConfiguration`, the TestContext framework
|
2018-08-30 23:29:17 +08:00
|
|
|
|
loads the corresponding `ApplicationContext` and stores it in a `static` context cache
|
|
|
|
|
under a key that is based solely on those locations. So, if `TestClassB` also defines
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`{"app-config.xml", "test-config.xml"}` for its locations (either explicitly or
|
|
|
|
|
implicitly through inheritance) but does not define `@WebAppConfiguration`, a different
|
|
|
|
|
`ContextLoader`, different active profiles, different context initializers, different
|
|
|
|
|
test property sources, or a different parent context, then the same `ApplicationContext`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
is shared by both test classes. This means that the setup cost for loading an application
|
|
|
|
|
context is incurred only once (per test suite), and subsequent test execution is much
|
|
|
|
|
faster.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Test suites and forked processes
|
|
|
|
|
[NOTE]
|
2015-03-06 23:57:32 +08:00
|
|
|
|
====
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The Spring TestContext framework stores application contexts in a static cache. This
|
2015-03-03 18:38:01 +08:00
|
|
|
|
means that the context is literally stored in a `static` variable. In other words, if
|
2018-08-30 23:29:17 +08:00
|
|
|
|
tests execute in separate processes, the static cache is cleared between each test
|
|
|
|
|
execution, which effectively disables the caching mechanism.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
To benefit from the caching mechanism, all tests must run within the same process or test
|
|
|
|
|
suite. This can be achieved by executing all tests as a group within an IDE. Similarly,
|
|
|
|
|
when executing tests with a build framework such as Ant, Maven, or Gradle, it is
|
|
|
|
|
important to make sure that the build framework does not fork between tests. For example,
|
|
|
|
|
if the
|
2019-03-21 06:48:14 +08:00
|
|
|
|
https://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html#forkMode[`forkMode`]
|
2018-09-18 22:42:09 +08:00
|
|
|
|
for the Maven Surefire plug-in is set to `always` or `pertest`, the TestContext framework
|
|
|
|
|
cannot cache application contexts between test classes, and the build process runs
|
|
|
|
|
significantly more slowly as a result.
|
2015-03-06 23:57:32 +08:00
|
|
|
|
====
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-09-26 21:02:30 +08:00
|
|
|
|
The size of the context cache is bounded with a default maximum size of 32. Whenever the
|
|
|
|
|
maximum size is reached, a least recently used (LRU) eviction policy is used to evict and
|
|
|
|
|
close stale contexts. You can configure the maximum size from the command line or a build
|
|
|
|
|
script by setting a JVM system property named `spring.test.context.cache.maxSize`. As an
|
|
|
|
|
alternative, you can set the same property programmatically by using the
|
|
|
|
|
`SpringProperties` API.
|
2016-04-06 21:06:10 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
Since having a large number of application contexts loaded within a given test suite can
|
|
|
|
|
cause the suite to take an unnecessarily long time to execute, it is often beneficial to
|
|
|
|
|
know exactly how many contexts have been loaded and cached. To view the statistics for
|
2018-08-30 23:29:17 +08:00
|
|
|
|
the underlying context cache, you can set the log level for the
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`org.springframework.test.context.cache` logging category to `DEBUG`.
|
|
|
|
|
|
|
|
|
|
In the unlikely case that a test corrupts the application context and requires reloading
|
2018-09-18 22:42:09 +08:00
|
|
|
|
(for example, by modifying a bean definition or the state of an application object), you
|
|
|
|
|
can annotate your test class or test method with `@DirtiesContext` (see the discussion of
|
2019-01-09 22:39:56 +08:00
|
|
|
|
`@DirtiesContext` in <<spring-testing-annotation-dirtiescontext>>). This instructs Spring
|
2018-09-18 22:42:09 +08:00
|
|
|
|
to remove the context from the cache and rebuild the application context before running
|
2019-01-09 22:39:56 +08:00
|
|
|
|
the next test that requires the same application context. Note that support for the
|
|
|
|
|
`@DirtiesContext` annotation is provided by the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`DirtiesContextBeforeModesTestExecutionListener` and the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`DirtiesContextTestExecutionListener`, which are enabled by default.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-ctx-management-ctx-hierarchies]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Context Hierarchies
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
When writing integration tests that rely on a loaded Spring `ApplicationContext`, it is
|
2018-08-30 23:29:17 +08:00
|
|
|
|
often sufficient to test against a single context. However, there are times when it is
|
2018-09-18 22:42:09 +08:00
|
|
|
|
beneficial or even necessary to test against a hierarchy of `ApplicationContext`
|
|
|
|
|
instances. For example, if you are developing a Spring MVC web application, you typically
|
|
|
|
|
have a root `WebApplicationContext` loaded by Spring's `ContextLoaderListener` and a
|
|
|
|
|
child `WebApplicationContext` loaded by Spring's `DispatcherServlet`. This results in a
|
2015-03-03 18:38:01 +08:00
|
|
|
|
parent-child context hierarchy where shared components and infrastructure configuration
|
|
|
|
|
are declared in the root context and consumed in the child context by web-specific
|
2018-08-30 23:29:17 +08:00
|
|
|
|
components. Another use case can be found in Spring Batch applications, where you often
|
2015-03-03 18:38:01 +08:00
|
|
|
|
have a parent context that provides configuration for shared batch infrastructure and a
|
|
|
|
|
child context for the configuration of a specific batch job.
|
|
|
|
|
|
2019-09-26 21:02:30 +08:00
|
|
|
|
You can write integration tests that use context hierarchies by declaring context
|
|
|
|
|
configuration with the `@ContextHierarchy` annotation, either on an individual test class
|
|
|
|
|
or within a test class hierarchy. If a context hierarchy is declared on multiple classes
|
|
|
|
|
within a test class hierarchy, you can also merge or override the context configuration
|
|
|
|
|
for a specific, named level in the context hierarchy. When merging configuration for a
|
|
|
|
|
given level in the hierarchy, the configuration resource type (that is, XML configuration
|
|
|
|
|
files or component classes) must be consistent. Otherwise, it is perfectly acceptable to
|
|
|
|
|
have different levels in a context hierarchy configured using different resource types.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
The remaining JUnit Jupiter based examples in this section show common configuration
|
|
|
|
|
scenarios for integration tests that require the use of context hierarchies.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Single test class with context hierarchy
|
|
|
|
|
--
|
|
|
|
|
`ControllerIntegrationTests` represents a typical integration testing scenario for a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Spring MVC web application by declaring a context hierarchy that consists of two levels,
|
|
|
|
|
one for the root `WebApplicationContext` (loaded by using the `TestAppConfig`
|
|
|
|
|
`@Configuration` class) and one for the dispatcher servlet `WebApplicationContext`
|
2018-09-18 22:42:09 +08:00
|
|
|
|
(loaded by using the `WebConfig` `@Configuration` class). The `WebApplicationContext`
|
|
|
|
|
that is autowired into the test instance is the one for the child context (that is, the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
lowest context in the hierarchy). The following listing shows this configuration scenario:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@WebAppConfiguration
|
|
|
|
|
@ContextHierarchy({
|
|
|
|
|
@ContextConfiguration(classes = TestAppConfig.class),
|
|
|
|
|
@ContextConfiguration(classes = WebConfig.class)
|
|
|
|
|
})
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ControllerIntegrationTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
WebApplicationContext wac;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
@WebAppConfiguration
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
ContextConfiguration(classes = [TestAppConfig::class]),
|
|
|
|
|
ContextConfiguration(classes = [WebConfig::class]))
|
|
|
|
|
class ControllerIntegrationTests {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var wac: WebApplicationContext
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.Class hierarchy with implicit parent context
|
|
|
|
|
--
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The test classes in this example define a context hierarchy within a test class
|
|
|
|
|
hierarchy. `AbstractWebTests` declares the configuration for a root
|
|
|
|
|
`WebApplicationContext` in a Spring-powered web application. Note, however, that
|
|
|
|
|
`AbstractWebTests` does not declare `@ContextHierarchy`. Consequently, subclasses of
|
|
|
|
|
`AbstractWebTests` can optionally participate in a context hierarchy or follow the
|
|
|
|
|
standard semantics for `@ContextConfiguration`. `SoapWebServiceTests` and
|
|
|
|
|
`RestWebServiceTests` both extend `AbstractWebTests` and define a context hierarchy by
|
|
|
|
|
using `@ContextHierarchy`. The result is that three application contexts are loaded (one
|
|
|
|
|
for each declaration of `@ContextConfiguration`), and the application context loaded
|
|
|
|
|
based on the configuration in `AbstractWebTests` is set as the parent context for each of
|
|
|
|
|
the contexts loaded for the concrete subclasses. The following listing shows this
|
|
|
|
|
configuration scenario:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@WebAppConfiguration
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
public abstract class AbstractWebTests {}
|
|
|
|
|
|
2019-08-21 20:18:21 +08:00
|
|
|
|
@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
|
2015-03-03 18:38:01 +08:00
|
|
|
|
public class SoapWebServiceTests extends AbstractWebTests {}
|
|
|
|
|
|
2019-08-21 20:18:21 +08:00
|
|
|
|
@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
|
2015-03-03 18:38:01 +08:00
|
|
|
|
public class RestWebServiceTests extends AbstractWebTests {}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
@WebAppConfiguration
|
|
|
|
|
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
|
|
|
|
|
abstract class AbstractWebTests
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml"))
|
|
|
|
|
class SoapWebServiceTests : AbstractWebTests()
|
|
|
|
|
|
|
|
|
|
@ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml"))
|
|
|
|
|
class RestWebServiceTests : AbstractWebTests()
|
|
|
|
|
|
|
|
|
|
----
|
|
|
|
|
--
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Class hierarchy with merged context hierarchy configuration
|
|
|
|
|
--
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The classes in this example show the use of named hierarchy levels in order to merge the
|
|
|
|
|
configuration for specific levels in a context hierarchy. `BaseTests` defines two levels
|
|
|
|
|
in the hierarchy, `parent` and `child`. `ExtendedTests` extends `BaseTests` and instructs
|
|
|
|
|
the Spring TestContext Framework to merge the context configuration for the `child`
|
|
|
|
|
hierarchy level, by ensuring that the names declared in the `name` attribute in
|
|
|
|
|
`@ContextConfiguration` are both `child`. The result is that three application contexts
|
|
|
|
|
are loaded: one for `/app-config.xml`, one for `/user-config.xml`, and one for
|
|
|
|
|
`{"/user-config.xml", "/order-config.xml"}`. As with the previous example, the
|
|
|
|
|
application context loaded from `/app-config.xml` is set as the parent context for the
|
|
|
|
|
contexts loaded from `/user-config.xml` and `{"/user-config.xml", "/order-config.xml"}`.
|
|
|
|
|
The following listing shows this configuration scenario:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@ContextHierarchy({
|
|
|
|
|
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
|
|
|
|
|
@ContextConfiguration(name = "child", locations = "/user-config.xml")
|
|
|
|
|
})
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class BaseTests {}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
@ContextConfiguration(name = "child", locations = "/order-config.xml")
|
|
|
|
|
)
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ExtendedTests extends BaseTests {}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
|
|
|
|
|
ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
|
|
|
|
|
open class BaseTests {}
|
|
|
|
|
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
ContextConfiguration(name = "child", locations = ["/order-config.xml"])
|
|
|
|
|
)
|
|
|
|
|
class ExtendedTests : BaseTests() {}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
--
|
|
|
|
|
|
|
|
|
|
.Class hierarchy with overridden context hierarchy configuration
|
|
|
|
|
--
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In contrast to the previous example, this example demonstrates how to override the
|
2015-06-21 22:25:00 +08:00
|
|
|
|
configuration for a given named level in a context hierarchy by setting the
|
|
|
|
|
`inheritLocations` flag in `@ContextConfiguration` to `false`. Consequently, the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
application context for `ExtendedTests` is loaded only from `/test-user-config.xml` and
|
|
|
|
|
has its parent set to the context loaded from `/app-config.xml`. The following listing
|
|
|
|
|
shows this configuration scenario:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@ContextHierarchy({
|
|
|
|
|
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
|
|
|
|
|
@ContextConfiguration(name = "child", locations = "/user-config.xml")
|
|
|
|
|
})
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class BaseTests {}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
@ContextConfiguration(
|
|
|
|
|
name = "child",
|
|
|
|
|
locations = "/test-user-config.xml",
|
|
|
|
|
inheritLocations = false
|
|
|
|
|
))
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class ExtendedTests extends BaseTests {}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
|
|
|
|
|
ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
|
|
|
|
|
open class BaseTests {}
|
|
|
|
|
|
|
|
|
|
@ContextHierarchy(
|
|
|
|
|
ContextConfiguration(
|
|
|
|
|
name = "child",
|
|
|
|
|
locations = ["/test-user-config.xml"],
|
|
|
|
|
inheritLocations = false
|
|
|
|
|
))
|
|
|
|
|
class ExtendedTests : BaseTests() {}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Dirtying a context within a context hierarchy
|
2018-09-18 22:42:09 +08:00
|
|
|
|
NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a
|
|
|
|
|
context hierarchy, you can use the `hierarchyMode` flag to control how the context cache
|
|
|
|
|
is cleared. For further details, see the discussion of `@DirtiesContext` in
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<spring-testing-annotation-dirtiescontext, Spring Testing Annotations>> and the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
--
|
|
|
|
|
|
|
|
|
|
[[testcontext-fixture-di]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Dependency Injection of Test Fixtures
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
When you use the `DependencyInjectionTestExecutionListener` (which is configured by
|
|
|
|
|
default), the dependencies of your test instances are injected from beans in the
|
2019-01-09 21:54:14 +08:00
|
|
|
|
application context that you configured with `@ContextConfiguration` or related
|
2019-03-05 20:08:34 +08:00
|
|
|
|
annotations. You may use setter injection, field injection, or both, depending on
|
|
|
|
|
which annotations you choose and whether you place them on setter methods or fields.
|
|
|
|
|
If you are using JUnit Jupiter you may also optionally use constructor injection
|
|
|
|
|
(see <<testcontext-junit-jupiter-di>>). For consistency with Spring's annotation-based
|
|
|
|
|
injection support, you may also use Spring's `@Autowired` annotation or the `@Inject`
|
|
|
|
|
annotation from JSR-330 for field and setter injection.
|
2019-01-09 21:54:14 +08:00
|
|
|
|
|
|
|
|
|
TIP: For testing frameworks other than JUnit Jupiter, the TestContext framework does not
|
|
|
|
|
participate in instantiation of the test class. Thus, the use of `@Autowired` or
|
|
|
|
|
`@Inject` for constructors has no effect for test classes.
|
|
|
|
|
|
|
|
|
|
NOTE: Although field injection is discouraged in production code, field injection is
|
|
|
|
|
actually quite natural in test code. The rationale for the difference is that you will
|
|
|
|
|
never instantiate your test class directly. Consequently, there is no need to be able to
|
|
|
|
|
invoke a `public` constructor or setter method on your test class.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Because `@Autowired` is used to perform <<core.adoc#beans-factory-autowire, autowiring by
|
|
|
|
|
type>>, if you have multiple bean definitions of the same type, you cannot rely on this
|
2015-03-03 18:38:01 +08:00
|
|
|
|
approach for those particular beans. In that case, you can use `@Autowired` in
|
2019-09-26 21:02:30 +08:00
|
|
|
|
conjunction with `@Qualifier`. You can also choose to use `@Inject` in conjunction with
|
|
|
|
|
`@Named`. Alternatively, if your test class has access to its `ApplicationContext`, you
|
|
|
|
|
can perform an explicit lookup by using (for example) a call to
|
2019-01-09 21:54:14 +08:00
|
|
|
|
`applicationContext.getBean("titleRepository", TitleRepository.class)`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If you do not want dependency injection applied to your test instances, do not annotate
|
|
|
|
|
fields or setter methods with `@Autowired` or `@Inject`. Alternatively, you can disable
|
|
|
|
|
dependency injection altogether by explicitly configuring your class with
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`@TestExecutionListeners` and omitting `DependencyInjectionTestExecutionListener.class`
|
|
|
|
|
from the list of listeners.
|
|
|
|
|
|
|
|
|
|
Consider the scenario of testing a `HibernateTitleRepository` class, as outlined in the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<integration-testing-goals, Goals>> section. The next two code listings demonstrate the
|
2015-03-03 18:38:01 +08:00
|
|
|
|
use of `@Autowired` on fields and setter methods. The application context configuration
|
|
|
|
|
is presented after all sample code listings.
|
|
|
|
|
|
|
|
|
|
[NOTE]
|
2015-03-06 23:57:32 +08:00
|
|
|
|
====
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The dependency injection behavior in the following code listings is not specific to JUnit
|
2019-08-21 21:54:28 +08:00
|
|
|
|
Jupiter. The same DI techniques can be used in conjunction with any supported testing
|
|
|
|
|
framework.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following examples make calls to static assertion methods, such as `assertNotNull()`,
|
2019-08-21 21:54:28 +08:00
|
|
|
|
but without prepending the call with `Assertions`. In such cases, assume that the method
|
|
|
|
|
was properly imported through an `import static` declaration that is not shown in the
|
|
|
|
|
example.
|
2015-03-06 23:57:32 +08:00
|
|
|
|
====
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
The first code listing shows a JUnit Jupiter based implementation of the test class that
|
|
|
|
|
uses `@Autowired` for field injection:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// specifies the Spring configuration to load for this test fixture
|
2019-03-13 22:12:51 +08:00
|
|
|
|
@ContextConfiguration("repository-config.xml")
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class HibernateTitleRepositoryTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// this instance will be dependency injected by type
|
2019-03-13 22:12:51 +08:00
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
HibernateTitleRepository titleRepository;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void findById() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
Title title = titleRepository.findById(new Long(10));
|
|
|
|
|
assertNotNull(title);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// specifies the Spring configuration to load for this test fixture
|
|
|
|
|
@ContextConfiguration("repository-config.xml")
|
|
|
|
|
class HibernateTitleRepositoryTests {
|
|
|
|
|
|
|
|
|
|
// this instance will be dependency injected by type
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var titleRepository: HibernateTitleRepository
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun findById() {
|
|
|
|
|
val title = titleRepository.findById(10)
|
|
|
|
|
assertNotNull(title)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Alternatively, you can configure the class to use `@Autowired` for setter injection, as
|
|
|
|
|
follows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@ExtendWith(SpringExtension.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// specifies the Spring configuration to load for this test fixture
|
2019-03-13 22:12:51 +08:00
|
|
|
|
@ContextConfiguration("repository-config.xml")
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class HibernateTitleRepositoryTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// this instance will be dependency injected by type
|
2019-08-21 21:54:28 +08:00
|
|
|
|
HibernateTitleRepository titleRepository;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-03-13 22:12:51 +08:00
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void setTitleRepository(HibernateTitleRepository titleRepository) {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
this.titleRepository = titleRepository;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void findById() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
Title title = titleRepository.findById(new Long(10));
|
|
|
|
|
assertNotNull(title);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// specifies the Spring configuration to load for this test fixture
|
|
|
|
|
@ContextConfiguration("repository-config.xml")
|
|
|
|
|
class HibernateTitleRepositoryTests {
|
|
|
|
|
|
|
|
|
|
// this instance will be dependency injected by type
|
|
|
|
|
lateinit var titleRepository: HibernateTitleRepository
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
fun setTitleRepository(titleRepository: HibernateTitleRepository) {
|
|
|
|
|
this.titleRepository = titleRepository
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun findById() {
|
|
|
|
|
val title = titleRepository.findById(10)
|
|
|
|
|
assertNotNull(title)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The preceding code listings use the same XML context file referenced by the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`@ContextConfiguration` annotation (that is, `repository-config.xml`). The following
|
|
|
|
|
shows this configuration:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[source,xml,indent=0]
|
|
|
|
|
[subs="verbatim,quotes"]
|
|
|
|
|
----
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
|
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
|
|
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
2019-03-21 06:48:14 +08:00
|
|
|
|
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
<!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
|
2019-03-13 22:12:51 +08:00
|
|
|
|
<bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
|
2015-03-03 18:38:01 +08:00
|
|
|
|
<property name="sessionFactory" ref="sessionFactory"/>
|
|
|
|
|
</bean>
|
|
|
|
|
|
2015-12-18 07:27:19 +08:00
|
|
|
|
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
2015-03-03 18:38:01 +08:00
|
|
|
|
<!-- configuration elided for brevity -->
|
|
|
|
|
</bean>
|
|
|
|
|
|
|
|
|
|
</beans>
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
[NOTE]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=====
|
2015-03-03 18:38:01 +08:00
|
|
|
|
If you are extending from a Spring-provided test base class that happens to use
|
|
|
|
|
`@Autowired` on one of its setter methods, you might have multiple beans of the affected
|
2018-08-30 23:29:17 +08:00
|
|
|
|
type defined in your application context (for example, multiple `DataSource` beans). In
|
2015-03-03 18:38:01 +08:00
|
|
|
|
such a case, you can override the setter method and use the `@Qualifier` annotation to
|
2018-08-30 23:29:17 +08:00
|
|
|
|
indicate a specific target bean, as follows (but make sure to delegate to the overridden
|
|
|
|
|
method in the superclass as well):
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
@Override
|
2019-03-13 22:12:51 +08:00
|
|
|
|
public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {
|
|
|
|
|
super.setDataSource(dataSource);
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
override fun setDataSource(@Qualifier("myDataSource") dataSource: DataSource) {
|
|
|
|
|
super.setDataSource(dataSource)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
----
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The specified qualifier value indicates the specific `DataSource` bean to inject,
|
|
|
|
|
narrowing the set of type matches to a specific bean. Its value is matched against
|
|
|
|
|
`<qualifier>` declarations within the corresponding `<bean>` definitions. The bean name
|
2018-08-30 23:29:17 +08:00
|
|
|
|
is used as a fallback qualifier value, so you can effectively also point to a specific
|
2018-09-18 22:42:09 +08:00
|
|
|
|
bean by name there (as shown earlier, assuming that `myDataSource` is the bean `id`).
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=====
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-web-scoped-beans]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Testing Request- and Session-scoped Beans
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
Spring has supported <<core#beans-factory-scopes-other, Request- and session-scoped
|
2019-09-26 21:02:30 +08:00
|
|
|
|
beans>> since the early years, and you can test your request-scoped and session-scoped
|
|
|
|
|
beans by following these steps:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Ensure that a `WebApplicationContext` is loaded for your test by annotating your test
|
2015-03-03 18:38:01 +08:00
|
|
|
|
class with `@WebAppConfiguration`.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Inject the mock request or session into your test instance and prepare your test
|
2015-03-03 18:38:01 +08:00
|
|
|
|
fixture as appropriate.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Invoke your web component that you retrieved from the configured
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`WebApplicationContext` (with dependency injection).
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Perform assertions against the mocks.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The next code snippet shows the XML configuration for a login use case. Note that the
|
|
|
|
|
`userService` bean has a dependency on a request-scoped `loginAction` bean. Also, the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
`LoginAction` is instantiated by using <<core.adoc#expressions, SpEL expressions>> that
|
2018-09-18 22:42:09 +08:00
|
|
|
|
retrieve the username and password from the current HTTP request. In our test, we want to
|
|
|
|
|
configure these request parameters through the mock managed by the TestContext framework.
|
|
|
|
|
The following listing shows the configuration for this use case:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Request-scoped bean configuration
|
|
|
|
|
[source,xml,indent=0]
|
|
|
|
|
----
|
|
|
|
|
<beans>
|
|
|
|
|
|
2018-01-08 06:33:35 +08:00
|
|
|
|
<bean id="userService" class="com.example.SimpleUserService"
|
|
|
|
|
c:loginAction-ref="loginAction"/>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
<bean id="loginAction" class="com.example.LoginAction"
|
2017-11-21 23:34:08 +08:00
|
|
|
|
c:username="#{request.getParameter('user')}"
|
2016-04-06 20:14:51 +08:00
|
|
|
|
c:password="#{request.getParameter('pswd')}"
|
2015-03-03 18:38:01 +08:00
|
|
|
|
scope="request">
|
2018-01-08 06:33:35 +08:00
|
|
|
|
<aop:scoped-proxy/>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
</bean>
|
|
|
|
|
|
|
|
|
|
</beans>
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In `RequestScopedBeanTests`, we inject both the `UserService` (that is, the subject under
|
2015-03-03 18:38:01 +08:00
|
|
|
|
test) and the `MockHttpServletRequest` into our test instance. Within our
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`requestScope()` test method, we set up our test fixture by setting request parameters in
|
2015-03-03 18:38:01 +08:00
|
|
|
|
the provided `MockHttpServletRequest`. When the `loginUser()` method is invoked on our
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`userService`, we are assured that the user service has access to the request-scoped
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`loginAction` for the current `MockHttpServletRequest` (that is, the one in which we just
|
|
|
|
|
set parameters). We can then perform assertions against the results based on the known
|
2018-08-30 23:29:17 +08:00
|
|
|
|
inputs for the username and password. The following listing shows how to do so:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Request-scoped bean test
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitWebConfig
|
|
|
|
|
class RequestScopedBeanTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired UserService userService;
|
|
|
|
|
@Autowired MockHttpServletRequest request;
|
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void requestScope() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
request.setParameter("user", "enigma");
|
|
|
|
|
request.setParameter("pswd", "$pr!ng");
|
|
|
|
|
|
|
|
|
|
LoginResults results = userService.loginUser();
|
|
|
|
|
// assert results
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitWebConfig
|
|
|
|
|
class RequestScopedBeanTests {
|
|
|
|
|
|
|
|
|
|
@Autowired lateinit var userService: UserService
|
|
|
|
|
@Autowired lateinit var request: MockHttpServletRequest
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun requestScope() {
|
|
|
|
|
request.setParameter("user", "enigma")
|
|
|
|
|
request.setParameter("pswd", "\$pr!ng")
|
|
|
|
|
|
|
|
|
|
val results = userService.loginUser()
|
|
|
|
|
// assert results
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following code snippet is similar to the one we saw earlier for a request-scoped
|
|
|
|
|
bean. However, this time, the `userService` bean has a dependency on a session-scoped
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`userPreferences` bean. Note that the `UserPreferences` bean is instantiated by using a
|
2018-09-18 22:42:09 +08:00
|
|
|
|
SpEL expression that retrieves the theme from the current HTTP session. In our test, we
|
|
|
|
|
need to configure a theme in the mock session managed by the TestContext framework. The
|
|
|
|
|
following example shows how to do so:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Session-scoped bean configuration
|
|
|
|
|
[source,xml,indent=0]
|
|
|
|
|
[subs="verbatim,quotes"]
|
|
|
|
|
----
|
|
|
|
|
<beans>
|
|
|
|
|
|
2018-01-08 06:33:35 +08:00
|
|
|
|
<bean id="userService" class="com.example.SimpleUserService"
|
2015-03-03 18:38:01 +08:00
|
|
|
|
c:userPreferences-ref="userPreferences" />
|
|
|
|
|
|
2018-01-08 06:33:35 +08:00
|
|
|
|
<bean id="userPreferences" class="com.example.UserPreferences"
|
2016-04-06 20:14:51 +08:00
|
|
|
|
c:theme="#{session.getAttribute('theme')}"
|
2015-03-03 18:38:01 +08:00
|
|
|
|
scope="session">
|
2018-01-08 06:33:35 +08:00
|
|
|
|
<aop:scoped-proxy/>
|
2015-03-03 18:38:01 +08:00
|
|
|
|
</bean>
|
|
|
|
|
|
|
|
|
|
</beans>
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In `SessionScopedBeanTests`, we inject the `UserService` and the `MockHttpSession` into
|
|
|
|
|
our test instance. Within our `sessionScope()` test method, we set up our test fixture by
|
|
|
|
|
setting the expected `theme` attribute in the provided `MockHttpSession`. When the
|
|
|
|
|
`processUserPreferences()` method is invoked on our `userService`, we are assured that
|
2015-03-03 18:38:01 +08:00
|
|
|
|
the user service has access to the session-scoped `userPreferences` for the current
|
|
|
|
|
`MockHttpSession`, and we can perform assertions against the results based on the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
configured theme. The following example shows how to do so:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
.Session-scoped bean test
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitWebConfig
|
|
|
|
|
class SessionScopedBeanTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired UserService userService;
|
|
|
|
|
@Autowired MockHttpSession session;
|
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void sessionScope() throws Exception {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
session.setAttribute("theme", "blue");
|
|
|
|
|
|
|
|
|
|
Results results = userService.processUserPreferences();
|
|
|
|
|
// assert results
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitWebConfig
|
|
|
|
|
class SessionScopedBeanTests {
|
|
|
|
|
|
|
|
|
|
@Autowired lateinit var userService: UserService
|
|
|
|
|
@Autowired lateinit var session: MockHttpSession
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun sessionScope() {
|
|
|
|
|
session.setAttribute("theme", "blue")
|
|
|
|
|
|
|
|
|
|
val results = userService.processUserPreferences()
|
|
|
|
|
// assert results
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-tx]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Transaction Management
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
In the TestContext framework, transactions are managed by the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`TransactionalTestExecutionListener`, which is configured by default, even if you do not
|
2015-03-03 18:38:01 +08:00
|
|
|
|
explicitly declare `@TestExecutionListeners` on your test class. To enable support for
|
|
|
|
|
transactions, however, you must configure a `PlatformTransactionManager` bean in the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`ApplicationContext` that is loaded with `@ContextConfiguration` semantics (further
|
|
|
|
|
details are provided later). In addition, you must declare Spring's `@Transactional`
|
|
|
|
|
annotation either at the class or the method level for your tests.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-tx-test-managed-transactions]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Test-managed Transactions
|
|
|
|
|
|
|
|
|
|
Test-managed transactions are transactions that are managed declaratively by using the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`TransactionalTestExecutionListener` or programmatically by using `TestTransaction`
|
|
|
|
|
(described later). You should not confuse such transactions with Spring-managed
|
|
|
|
|
transactions (those managed directly by Spring within the `ApplicationContext` loaded for
|
|
|
|
|
tests) or application-managed transactions (those managed programmatically within
|
2018-08-30 23:29:17 +08:00
|
|
|
|
application code that is invoked by tests). Spring-managed and application-managed
|
2018-09-18 22:42:09 +08:00
|
|
|
|
transactions typically participate in test-managed transactions. However, you should use
|
|
|
|
|
caution if Spring-managed or application-managed transactions are configured with any
|
|
|
|
|
propagation type other than `REQUIRED` or `SUPPORTS` (see the discussion on
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<data-access.adoc#tx-propagation, transaction propagation>> for details).
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-01-09 23:37:56 +08:00
|
|
|
|
.Preemptive timeouts and test-managed transactions
|
|
|
|
|
[WARNING]
|
|
|
|
|
====
|
|
|
|
|
Caution must be taken when using any form of preemptive timeouts from a testing framework
|
|
|
|
|
in conjunction with Spring's test-managed transactions.
|
|
|
|
|
|
|
|
|
|
Specifically, Spring’s testing support binds transaction state to the current thread (via
|
|
|
|
|
a `java.lang.ThreadLocal` variable) _before_ the current test method is invoked. If a
|
|
|
|
|
testing framework invokes the current test method in a new thread in order to support a
|
|
|
|
|
preemptive timeout, any actions performed within the current test method will _not_ be
|
|
|
|
|
invoked within the test-managed transaction. Consequently, the result of any such actions
|
|
|
|
|
will not be rolled back with the test-managed transaction. On the contrary, such actions
|
|
|
|
|
will be committed to the persistent store -- for example, a relational database -- even
|
|
|
|
|
though the test-managed transaction is properly rolled back by Spring.
|
|
|
|
|
|
|
|
|
|
Situations in which this can occur include but are not limited to the following.
|
|
|
|
|
|
|
|
|
|
* JUnit 4's `@Test(timeout = ...)` support and `TimeOut` rule
|
|
|
|
|
* JUnit Jupiter's `assertTimeoutPreemptively(...)` methods in the
|
|
|
|
|
`org.junit.jupiter.api.Assertions` class
|
|
|
|
|
* TestNG's `@Test(timeOut = ...)` support
|
|
|
|
|
====
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-tx-enabling-transactions]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Enabling and Disabling Transactions
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Annotating a test method with `@Transactional` causes the test to be run within a
|
2018-09-18 22:42:09 +08:00
|
|
|
|
transaction that is, by default, automatically rolled back after completion of the test.
|
|
|
|
|
If a test class is annotated with `@Transactional`, each test method within that class
|
|
|
|
|
hierarchy runs within a transaction. Test methods that are not annotated with
|
2019-02-23 02:19:59 +08:00
|
|
|
|
`@Transactional` (at the class or method level) are not run within a transaction. Note
|
|
|
|
|
that `@Transactional` is not supported on test lifecycle methods — for example, methods
|
|
|
|
|
annotated with JUnit Jupiter's `@BeforeAll`, `@BeforeEach`, etc. Furthermore, tests that
|
2019-06-18 19:27:09 +08:00
|
|
|
|
are annotated with `@Transactional` but have the `propagation` attribute set to
|
2019-02-23 02:19:59 +08:00
|
|
|
|
`NOT_SUPPORTED` are not run within a transaction.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-06-18 19:27:09 +08:00
|
|
|
|
[[testcontext-tx-attribute-support]]
|
|
|
|
|
.`@Transactional` attribute support
|
|
|
|
|
|===
|
|
|
|
|
|Attribute |Supported for test-managed transactions
|
|
|
|
|
|
|
|
|
|
|`value` and `transactionManager` |yes
|
|
|
|
|
|
|
|
|
|
|`propagation` |only `Propagation.NOT_SUPPORTED` is supported
|
|
|
|
|
|
|
|
|
|
|`isolation` |no
|
|
|
|
|
|
|
|
|
|
|`timeout` |no
|
|
|
|
|
|
|
|
|
|
|`readOnly` |no
|
|
|
|
|
|
|
|
|
|
|`rollbackFor` and `rollbackForClassName` |no: use `TestTransaction.flagForRollback()` instead
|
|
|
|
|
|
|
|
|
|
|`noRollbackFor` and `noRollbackForClassName` |no: use `TestTransaction.flagForCommit()` instead
|
|
|
|
|
|===
|
|
|
|
|
|
2014-02-06 03:12:31 +08:00
|
|
|
|
[TIP]
|
|
|
|
|
====
|
2019-02-23 02:19:59 +08:00
|
|
|
|
Method-level lifecycle methods — for example, methods annotated with JUnit Jupiter's
|
|
|
|
|
`@BeforeEach` or `@AfterEach` — are run within a test-managed transaction. On the other
|
|
|
|
|
hand, suite-level and class-level lifecycle methods — for example, methods annotated with
|
|
|
|
|
JUnit Jupiter's `@BeforeAll` or `@AfterAll` and methods annotated with TestNG's
|
|
|
|
|
`@BeforeSuite`, `@AfterSuite`, `@BeforeClass`, or `@AfterClass` — are _not_ run within a
|
|
|
|
|
test-managed transaction.
|
|
|
|
|
|
|
|
|
|
If you need to execute code in a suite-level or class-level lifecycle method within a
|
|
|
|
|
transaction, you may wish to inject a corresponding `PlatformTransactionManager` into
|
|
|
|
|
your test class and then use that with a `TransactionTemplate` for programmatic
|
|
|
|
|
transaction management.
|
2014-02-06 03:12:31 +08:00
|
|
|
|
====
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Note that <<testcontext-support-classes-junit4,
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`AbstractTransactionalJUnit4SpringContextTests`>> and
|
|
|
|
|
<<testcontext-support-classes-testng, `AbstractTransactionalTestNGSpringContextTests`>>
|
2018-08-30 23:29:17 +08:00
|
|
|
|
are preconfigured for transactional support at the class level.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
The following example demonstrates a common scenario for writing an integration test for
|
2018-08-30 23:29:17 +08:00
|
|
|
|
a Hibernate-based `UserRepository`:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitConfig(TestConfig.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Transactional
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class HibernateUserRepositoryTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
HibernateUserRepository repository;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
SessionFactory sessionFactory;
|
|
|
|
|
|
|
|
|
|
JdbcTemplate jdbcTemplate;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void setDataSource(DataSource dataSource) {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void createUser() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// track initial state in test database:
|
|
|
|
|
final int count = countRowsInTable("user");
|
|
|
|
|
|
|
|
|
|
User user = new User(...);
|
|
|
|
|
repository.save(user);
|
|
|
|
|
|
|
|
|
|
// Manual flush is required to avoid false positive in test
|
|
|
|
|
sessionFactory.getCurrentSession().flush();
|
|
|
|
|
assertNumUsers(count + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
private int countRowsInTable(String tableName) {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
private void assertNumUsers(int expected) {
|
2015-06-21 22:25:00 +08:00
|
|
|
|
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
|
|
|
@Transactional
|
|
|
|
|
class HibernateUserRepositoryTests {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var repository: HibernateUserRepository
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var sessionFactory: SessionFactory
|
|
|
|
|
|
|
|
|
|
lateinit var jdbcTemplate: JdbcTemplate
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
fun setDataSource(dataSource: DataSource) {
|
|
|
|
|
this.jdbcTemplate = JdbcTemplate(dataSource)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun createUser() {
|
|
|
|
|
// track initial state in test database:
|
|
|
|
|
val count = countRowsInTable("user")
|
|
|
|
|
|
|
|
|
|
val user = User()
|
|
|
|
|
repository.save(user)
|
|
|
|
|
|
|
|
|
|
// Manual flush is required to avoid false positive in test
|
|
|
|
|
sessionFactory.getCurrentSession().flush()
|
|
|
|
|
assertNumUsers(count + 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun countRowsInTable(tableName: String): Int {
|
|
|
|
|
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun assertNumUsers(expected: Int) {
|
|
|
|
|
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
As explained in <<testcontext-tx-rollback-and-commit-behavior>>, there is no need to
|
|
|
|
|
clean up the database after the `createUser()` method runs, since any changes made to the
|
2019-01-23 23:40:21 +08:00
|
|
|
|
database are automatically rolled back by the `TransactionalTestExecutionListener`.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-tx-rollback-and-commit-behavior]]
|
2018-09-18 22:42:09 +08:00
|
|
|
|
===== Transaction Rollback and Commit Behavior
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
By default, test transactions will be automatically rolled back after completion of the
|
|
|
|
|
test; however, transactional commit and rollback behavior can be configured declaratively
|
2015-09-11 01:35:17 +08:00
|
|
|
|
via the `@Commit` and `@Rollback` annotations. See the corresponding entries in the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<integration-testing-annotations, annotation support>> section for further details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-tx-programmatic-tx-mgt]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Programmatic Transaction Management
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2019-09-26 21:02:30 +08:00
|
|
|
|
You can interact with test-managed transactions programmatically by using the static
|
|
|
|
|
methods in `TestTransaction`. For example, you can use `TestTransaction` within test
|
|
|
|
|
methods, before methods, and after methods to start or end the current test-managed
|
|
|
|
|
transaction or to configure the current test-managed transaction for rollback or commit.
|
|
|
|
|
Support for `TestTransaction` is automatically available whenever the
|
|
|
|
|
`TransactionalTestExecutionListener` is enabled.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
The following example demonstrates some of the features of `TestTransaction`. See the
|
|
|
|
|
javadoc for {api-spring-framework}/test/context/transaction/TestTransaction.html[`TestTransaction`]
|
|
|
|
|
for further details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration(classes = TestConfig.class)
|
|
|
|
|
public class ProgrammaticTransactionManagementTests extends
|
|
|
|
|
AbstractTransactionalJUnit4SpringContextTests {
|
2017-11-03 05:37:47 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Test
|
|
|
|
|
public void transactionalTest() {
|
|
|
|
|
// assert initial state in test database:
|
|
|
|
|
assertNumUsers(2);
|
|
|
|
|
|
|
|
|
|
deleteFromTables("user");
|
|
|
|
|
|
|
|
|
|
// changes to the database will be committed!
|
|
|
|
|
TestTransaction.flagForCommit();
|
|
|
|
|
TestTransaction.end();
|
|
|
|
|
assertFalse(TestTransaction.isActive());
|
|
|
|
|
assertNumUsers(0);
|
|
|
|
|
|
|
|
|
|
TestTransaction.start();
|
|
|
|
|
// perform other actions against the database that will
|
|
|
|
|
// be automatically rolled back after the test completes...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void assertNumUsers(int expected) {
|
2015-06-21 22:25:00 +08:00
|
|
|
|
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@ContextConfiguration(classes = [TestConfig::class])
|
|
|
|
|
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun transactionalTest() {
|
|
|
|
|
// assert initial state in test database:
|
|
|
|
|
assertNumUsers(2)
|
|
|
|
|
|
|
|
|
|
deleteFromTables("user")
|
|
|
|
|
|
|
|
|
|
// changes to the database will be committed!
|
|
|
|
|
TestTransaction.flagForCommit()
|
|
|
|
|
TestTransaction.end()
|
|
|
|
|
assertFalse(TestTransaction.isActive())
|
|
|
|
|
assertNumUsers(0)
|
|
|
|
|
|
|
|
|
|
TestTransaction.start()
|
|
|
|
|
// perform other actions against the database that will
|
|
|
|
|
// be automatically rolled back after the test completes...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected fun assertNumUsers(expected: Int) {
|
|
|
|
|
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-tx-before-and-after-tx]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Running Code Outside of a Transaction
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Occasionally, you may need to execute certain code before or after a transactional test
|
|
|
|
|
method but outside the transactional context -- for example, to verify the initial
|
|
|
|
|
database state prior to running your test or to verify expected transactional commit
|
|
|
|
|
behavior after your test runs (if the test was configured to commit the transaction).
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`TransactionalTestExecutionListener` supports the `@BeforeTransaction` and
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`@AfterTransaction` annotations for exactly such scenarios. You can annotate any `void`
|
2016-05-04 00:53:17 +08:00
|
|
|
|
method in a test class or any `void` default method in a test interface with one of these
|
2018-08-30 23:29:17 +08:00
|
|
|
|
annotations, and the `TransactionalTestExecutionListener` ensures that your before
|
|
|
|
|
transaction method or after transaction method runs at the appropriate time.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
TIP: Any before methods (such as methods annotated with JUnit Jupiter's `@BeforeEach`)
|
|
|
|
|
and any after methods (such as methods annotated with JUnit Jupiter's `@AfterEach`) are
|
|
|
|
|
run within a transaction. In addition, methods annotated with `@BeforeTransaction` or
|
|
|
|
|
`@AfterTransaction` are not run for test methods that are not configured to run within a
|
|
|
|
|
transaction.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-tx-mgr-config]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Configuring a Transaction Manager
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
`TransactionalTestExecutionListener` expects a `PlatformTransactionManager` bean to be
|
2018-09-18 22:42:09 +08:00
|
|
|
|
defined in the Spring `ApplicationContext` for the test. If there are multiple instances
|
|
|
|
|
of `PlatformTransactionManager` within the test's `ApplicationContext`, you can declare a
|
|
|
|
|
qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager =
|
|
|
|
|
"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an
|
|
|
|
|
`@Configuration` class. Consult the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
|
2018-09-18 22:42:09 +08:00
|
|
|
|
for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the
|
|
|
|
|
algorithm used to look up a transaction manager in the test's `ApplicationContext`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-tx-annotation-demo]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Demonstration of All Transaction-related Annotations
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
The following JUnit Jupiter based example displays a fictitious integration testing
|
|
|
|
|
scenario that highlights all transaction-related annotations. The example is not intended
|
|
|
|
|
to demonstrate best practices but rather to demonstrate how these annotations can be
|
|
|
|
|
used. See the <<integration-testing-annotations, annotation support>> section for further
|
2015-03-03 18:38:01 +08:00
|
|
|
|
information and configuration examples. <<testcontext-executing-sql-declaratively-tx,
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Transaction management for `@Sql`>> contains an additional example that uses `@Sql` for
|
|
|
|
|
declarative SQL script execution with default transaction rollback semantics. The
|
2019-03-13 22:28:15 +08:00
|
|
|
|
following example shows the relevant annotations:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitConfig
|
2019-03-13 22:12:51 +08:00
|
|
|
|
@Transactional(transactionManager = "txMgr")
|
|
|
|
|
@Commit
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class FictitiousTransactionalTest {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-03-13 22:12:51 +08:00
|
|
|
|
@BeforeTransaction
|
2016-02-29 06:36:21 +08:00
|
|
|
|
void verifyInitialDatabaseState() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// logic to verify the initial state before a transaction is started
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setUpTestDataWithinTransaction() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// set up test data within the transaction
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
2015-10-16 22:08:42 +08:00
|
|
|
|
// overrides the class-level @Commit setting
|
2019-03-13 22:12:51 +08:00
|
|
|
|
@Rollback
|
2019-08-21 21:54:28 +08:00
|
|
|
|
void modifyDatabaseWithinTransaction() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// logic which uses the test data and modifies database state
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@AfterEach
|
|
|
|
|
void tearDownWithinTransaction() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute "tear down" logic within the transaction
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-13 22:12:51 +08:00
|
|
|
|
@AfterTransaction
|
2016-02-29 06:36:21 +08:00
|
|
|
|
void verifyFinalDatabaseState() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// logic to verify the final state after transaction has rolled back
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig
|
|
|
|
|
@Transactional(transactionManager = "txMgr")
|
|
|
|
|
@Commit
|
|
|
|
|
class FictitiousTransactionalTest {
|
|
|
|
|
|
|
|
|
|
@BeforeTransaction
|
|
|
|
|
fun verifyInitialDatabaseState() {
|
|
|
|
|
// logic to verify the initial state before a transaction is started
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setUpTestDataWithinTransaction() {
|
|
|
|
|
// set up test data within the transaction
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
// overrides the class-level @Commit setting
|
|
|
|
|
@Rollback
|
|
|
|
|
fun modifyDatabaseWithinTransaction() {
|
|
|
|
|
// logic which uses the test data and modifies database state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
|
fun tearDownWithinTransaction() {
|
|
|
|
|
// execute "tear down" logic within the transaction
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@AfterTransaction
|
|
|
|
|
fun verifyFinalDatabaseState() {
|
|
|
|
|
// logic to verify the final state after transaction has rolled back
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-tx-false-positives]]
|
|
|
|
|
.Avoid false positives when testing ORM code
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[NOTE]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=====
|
2016-05-04 01:33:32 +08:00
|
|
|
|
When you test application code that manipulates the state of a Hibernate session or JPA
|
2018-09-18 22:42:09 +08:00
|
|
|
|
persistence context, make sure to flush the underlying unit of work within test methods
|
|
|
|
|
that run that code. Failing to flush the underlying unit of work can produce false
|
|
|
|
|
positives: Your test passes, but the same code throws an exception in a live, production
|
|
|
|
|
environment. Note that this applies to any ORM framework that maintains an in-memory unit
|
|
|
|
|
of work. In the following Hibernate-based example test case, one method demonstrates a
|
|
|
|
|
false positive, and the other method correctly exposes the results of flushing the
|
|
|
|
|
session:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
|
|
|
|
----
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
SessionFactory sessionFactory;
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Test // no expected exception!
|
|
|
|
|
public void falsePositive() {
|
|
|
|
|
updateEntityInHibernateSession();
|
|
|
|
|
// False positive: an exception will be thrown once the Hibernate
|
|
|
|
|
// Session is finally flushed (i.e., in production code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Test(expected = ...)
|
|
|
|
|
public void updateWithSessionFlush() {
|
|
|
|
|
updateEntityInHibernateSession();
|
|
|
|
|
// Manual flush is required to avoid false positive in test
|
|
|
|
|
sessionFactory.getCurrentSession().flush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
@Autowired
|
2019-08-30 08:35:53 +08:00
|
|
|
|
lateinit var sessionFactory: SessionFactory
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2016-05-04 01:33:32 +08:00
|
|
|
|
@Transactional
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Test // no expected exception!
|
2019-08-30 08:35:53 +08:00
|
|
|
|
fun falsePositive() {
|
|
|
|
|
updateEntityInHibernateSession()
|
2015-11-25 23:58:17 +08:00
|
|
|
|
// False positive: an exception will be thrown once the Hibernate
|
|
|
|
|
// Session is finally flushed (i.e., in production code)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-04 01:33:32 +08:00
|
|
|
|
@Transactional
|
2015-11-25 23:58:17 +08:00
|
|
|
|
@Test(expected = ...)
|
2019-08-30 08:35:53 +08:00
|
|
|
|
fun updateWithSessionFlush() {
|
|
|
|
|
updateEntityInHibernateSession()
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// Manual flush is required to avoid false positive in test
|
2019-08-30 08:35:53 +08:00
|
|
|
|
sessionFactory.getCurrentSession().flush()
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
----
|
2015-11-25 23:58:17 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following example shows matching methods for JPA:
|
2015-11-25 23:58:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-11-25 23:58:17 +08:00
|
|
|
|
----
|
|
|
|
|
// ...
|
|
|
|
|
|
2016-05-04 01:33:32 +08:00
|
|
|
|
@PersistenceContext
|
|
|
|
|
EntityManager entityManager;
|
2015-11-25 23:58:17 +08:00
|
|
|
|
|
2016-05-04 01:33:32 +08:00
|
|
|
|
@Transactional
|
2015-11-25 23:58:17 +08:00
|
|
|
|
@Test // no expected exception!
|
|
|
|
|
public void falsePositive() {
|
2016-05-04 01:33:32 +08:00
|
|
|
|
updateEntityInJpaPersistenceContext();
|
2015-11-25 23:58:17 +08:00
|
|
|
|
// False positive: an exception will be thrown once the JPA
|
|
|
|
|
// EntityManager is finally flushed (i.e., in production code)
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-04 01:33:32 +08:00
|
|
|
|
@Transactional
|
2015-11-25 23:58:17 +08:00
|
|
|
|
@Test(expected = ...)
|
|
|
|
|
public void updateWithEntityManagerFlush() {
|
2016-05-04 01:33:32 +08:00
|
|
|
|
updateEntityInJpaPersistenceContext();
|
2015-11-25 23:58:17 +08:00
|
|
|
|
// Manual flush is required to avoid false positive in test
|
|
|
|
|
entityManager.flush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
@PersistenceContext
|
|
|
|
|
lateinit var entityManager:EntityManager
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Test // no expected exception!
|
|
|
|
|
fun falsePositive() {
|
|
|
|
|
updateEntityInJpaPersistenceContext()
|
|
|
|
|
// False positive: an exception will be thrown once the JPA
|
|
|
|
|
// EntityManager is finally flushed (i.e., in production code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Test(expected = ...)
|
|
|
|
|
void updateWithEntityManagerFlush() {
|
|
|
|
|
updateEntityInJpaPersistenceContext()
|
|
|
|
|
// Manual flush is required to avoid false positive in test
|
|
|
|
|
entityManager.flush()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
=====
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-executing-sql]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Executing SQL Scripts
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
When writing integration tests against a relational database, it is often beneficial to
|
|
|
|
|
execute SQL scripts to modify the database schema or insert test data into tables. The
|
|
|
|
|
`spring-jdbc` module provides support for _initializing_ an embedded or existing database
|
|
|
|
|
by executing SQL scripts when the Spring `ApplicationContext` is loaded. 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>> for details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Although it is very useful to initialize a database for testing _once_ when the
|
|
|
|
|
`ApplicationContext` is loaded, sometimes it is essential to be able to modify the
|
|
|
|
|
database _during_ integration tests. The following sections explain how to execute SQL
|
|
|
|
|
scripts programmatically and declaratively during integration tests.
|
|
|
|
|
|
|
|
|
|
[[testcontext-executing-sql-programmatically]]
|
|
|
|
|
===== Executing SQL scripts programmatically
|
|
|
|
|
|
|
|
|
|
Spring provides the following options for executing SQL scripts programmatically within
|
|
|
|
|
integration test methods.
|
|
|
|
|
|
|
|
|
|
* `org.springframework.jdbc.datasource.init.ScriptUtils`
|
|
|
|
|
* `org.springframework.jdbc.datasource.init.ResourceDatabasePopulator`
|
|
|
|
|
* `org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests`
|
|
|
|
|
* `org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests`
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ScriptUtils` provides a collection of static utility methods for working with SQL
|
|
|
|
|
scripts and is mainly intended for internal use within the framework. However, if you
|
|
|
|
|
require full control over how SQL scripts are parsed and executed, `ScriptUtils` may suit
|
|
|
|
|
your needs better than some of the other alternatives described later. See the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/jdbc/datasource/init/ScriptUtils.html[javadoc] for individual
|
2018-09-18 22:42:09 +08:00
|
|
|
|
methods in `ScriptUtils` for further details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ResourceDatabasePopulator` provides an object-based API for programmatically populating,
|
|
|
|
|
initializing, or cleaning up a database by using SQL scripts defined in external
|
|
|
|
|
resources. `ResourceDatabasePopulator` provides options for configuring the character
|
|
|
|
|
encoding, statement separator, comment delimiters, and error handling flags used when
|
|
|
|
|
parsing and running the scripts. Each of the configuration options has a reasonable
|
|
|
|
|
default value. See the
|
2018-10-25 21:15:58 +08:00
|
|
|
|
{api-spring-framework}/jdbc/datasource/init/ResourceDatabasePopulator.html[javadoc] for
|
2018-09-18 22:42:09 +08:00
|
|
|
|
details on default values. To run the scripts configured in a
|
|
|
|
|
`ResourceDatabasePopulator`, you can invoke either the `populate(Connection)` method to
|
|
|
|
|
execute the populator against a `java.sql.Connection` or the `execute(DataSource)` method
|
|
|
|
|
to execute the populator against a `javax.sql.DataSource`. The following example
|
|
|
|
|
specifies SQL scripts for a test schema and test data, sets the statement separator to
|
|
|
|
|
`@@`, and executes the scripts against a `DataSource`:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Test
|
2019-08-30 23:19:46 +08:00
|
|
|
|
void databaseTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
|
|
|
|
populator.addScripts(
|
2017-11-21 23:34:08 +08:00
|
|
|
|
new ClassPathResource("test-schema.sql"),
|
|
|
|
|
new ClassPathResource("test-data.sql"));
|
2015-03-03 18:38:01 +08:00
|
|
|
|
populator.setSeparator("@@");
|
|
|
|
|
populator.execute(this.dataSource);
|
|
|
|
|
// execute code that uses the test schema and data
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
fun databaseTest() {
|
|
|
|
|
val populator = ResourceDatabasePopulator()
|
|
|
|
|
populator.addScripts(
|
|
|
|
|
ClassPathResource("test-schema.sql"),
|
|
|
|
|
ClassPathResource("test-data.sql"))
|
|
|
|
|
populator.setSeparator("@@")
|
|
|
|
|
populator.execute(dataSource)
|
|
|
|
|
// execute code that uses the test schema and data
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
Note that `ResourceDatabasePopulator` internally delegates to `ScriptUtils` for parsing
|
2018-08-30 23:29:17 +08:00
|
|
|
|
and running SQL scripts. Similarly, the `executeSqlScript(..)` methods in
|
2018-09-18 22:42:09 +08:00
|
|
|
|
<<testcontext-support-classes-junit4, `AbstractTransactionalJUnit4SpringContextTests`>>
|
2019-03-05 20:08:34 +08:00
|
|
|
|
and <<testcontext-support-classes-testng, `AbstractTransactionalTestNGSpringContextTests`>>
|
2018-10-25 21:15:58 +08:00
|
|
|
|
internally use a `ResourceDatabasePopulator` to run SQL scripts. See the javadoc for the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
various `executeSqlScript(..)` methods for further details.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[testcontext-executing-sql-declaratively]]
|
2016-05-04 22:04:37 +08:00
|
|
|
|
===== Executing SQL scripts declaratively with @Sql
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In addition to the aforementioned mechanisms for running SQL scripts programmatically,
|
|
|
|
|
you can declaratively configure SQL scripts in the Spring TestContext Framework.
|
|
|
|
|
Specifically, you can declare the `@Sql` annotation on a test class or test method to
|
2019-07-21 21:37:51 +08:00
|
|
|
|
configure individual SQL statements or the resource paths to SQL scripts that should be
|
|
|
|
|
run against a given database before or after an integration test method. Support for
|
|
|
|
|
`@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled by default.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
NOTE: Method-level `@Sql` declarations override class-level declarations by default. As
|
|
|
|
|
of Spring Framework 5.2, however, this behavior may be configured per test class or per
|
|
|
|
|
test method via `@SqlMergeMode`. See
|
|
|
|
|
<<testcontext-executing-sql-declaratively-script-merging>> for further details.
|
|
|
|
|
|
|
|
|
|
[[testcontext-executing-sql-declaratively-script-resources]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Path Resource Semantics
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Each path is interpreted as a Spring `Resource`. A plain path (for example,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`"schema.sql"`) is treated as a classpath resource that is relative to the package in
|
|
|
|
|
which the test class is defined. A path starting with a slash is treated as an absolute
|
|
|
|
|
classpath resource (for example, `"/org/example/schema.sql"`). A path that references a
|
|
|
|
|
URL (for example, a path prefixed with `classpath:`, `file:`, `http:`) is loaded by using
|
|
|
|
|
the specified resource protocol.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following example shows how to use `@Sql` at the class level and at the method level
|
|
|
|
|
within a JUnit Jupiter based integration test class:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2017-09-28 05:27:44 +08:00
|
|
|
|
@SpringJUnitConfig
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@Sql("/test-schema.sql")
|
2017-09-28 05:27:44 +08:00
|
|
|
|
class DatabaseTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Test
|
2019-08-30 23:19:46 +08:00
|
|
|
|
void emptySchemaTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute code that uses the test schema without any test data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@Sql({"/test-schema.sql", "/test-user-data.sql"})
|
2019-08-30 23:12:23 +08:00
|
|
|
|
void userTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute code that uses the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig
|
|
|
|
|
@Sql("/test-schema.sql")
|
|
|
|
|
class DatabaseTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun emptySchemaTest() {
|
|
|
|
|
// execute code that uses the test schema without any test data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Sql("/test-schema.sql", "/test-user-data.sql")
|
|
|
|
|
fun userTest() {
|
|
|
|
|
// execute code that uses the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
[[testcontext-executing-sql-declaratively-script-detection]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Default Script Detection
|
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
If no SQL scripts or statements are specified, an attempt is made to detect a `default`
|
|
|
|
|
script, depending on where `@Sql` is declared. If a default cannot be detected, an
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`IllegalStateException` is thrown.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* Class-level declaration: If the annotated test class is `com.example.MyTest`, the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
corresponding default script is `classpath:com/example/MyTest.sql`.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* Method-level declaration: If the annotated test method is named `testMethod()` and is
|
2018-09-18 22:42:09 +08:00
|
|
|
|
defined in the class `com.example.MyTest`, the corresponding default script is
|
|
|
|
|
`classpath:com/example/MyTest.testMethod.sql`.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
[[testcontext-executing-sql-declaratively-multiple-annotations]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Declaring Multiple `@Sql` Sets
|
|
|
|
|
|
|
|
|
|
If you need to configure multiple sets of SQL scripts for a given test class or test
|
2015-03-03 18:38:01 +08:00
|
|
|
|
method but with different syntax configuration, different error handling rules, or
|
2018-09-18 22:42:09 +08:00
|
|
|
|
different execution phases per set, you can declare multiple instances of `@Sql`. With
|
|
|
|
|
Java 8, you can use `@Sql` as a repeatable annotation. Otherwise, you can use the
|
|
|
|
|
`@SqlGroup` annotation as an explicit container for declaring multiple instances of
|
|
|
|
|
`@Sql`.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following example shows how to use `@Sql` as a repeatable annotation with Java 8:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@Sql("/test-user-data.sql")
|
2019-08-30 23:12:23 +08:00
|
|
|
|
void userTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute code that uses the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In the scenario presented in the preceding example, the `test-schema.sql` script uses a
|
|
|
|
|
different syntax for single-line comments.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following example is identical to the preceding example, except that the `@Sql`
|
2019-08-31 20:47:49 +08:00
|
|
|
|
declarations are grouped together within `@SqlGroup`. With Java 8 and above, the use of
|
|
|
|
|
`@SqlGroup` is optional, but you may need to use `@SqlGroup` for compatibility with
|
|
|
|
|
other JVM languages such as Kotlin.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
@SqlGroup({
|
|
|
|
|
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@Sql("/test-user-data.sql")
|
2015-03-03 18:38:01 +08:00
|
|
|
|
)}
|
2019-08-30 23:12:23 +08:00
|
|
|
|
void userTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute code that uses the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
@SqlGroup(
|
|
|
|
|
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
|
|
|
|
|
Sql("/test-user-data.sql"))
|
|
|
|
|
fun userTest() {
|
|
|
|
|
// execute code that uses the test schema and test data
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
[[testcontext-executing-sql-declaratively-script-execution-phases]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Script Execution Phases
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
By default, SQL scripts are executed before the corresponding test method. However, if
|
|
|
|
|
you need to run a particular set of scripts after the test method (for example, to clean
|
|
|
|
|
up database state), you can use the `executionPhase` attribute in `@Sql`, as the
|
|
|
|
|
following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
@Sql(
|
|
|
|
|
scripts = "create-test-data.sql",
|
|
|
|
|
config = @SqlConfig(transactionMode = ISOLATED)
|
|
|
|
|
)
|
|
|
|
|
@Sql(
|
|
|
|
|
scripts = "delete-test-data.sql",
|
|
|
|
|
config = @SqlConfig(transactionMode = ISOLATED),
|
|
|
|
|
executionPhase = AFTER_TEST_METHOD
|
|
|
|
|
)
|
2019-08-30 23:12:23 +08:00
|
|
|
|
void userTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// execute code that needs the test data to be committed
|
|
|
|
|
// to the database outside of the test's transaction
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
2019-08-31 20:47:49 +08:00
|
|
|
|
@Test
|
|
|
|
|
@SqlGroup(
|
|
|
|
|
Sql("create-test-data.sql",
|
|
|
|
|
config = SqlConfig(transactionMode = ISOLATED)),
|
|
|
|
|
Sql("delete-test-data.sql",
|
|
|
|
|
config = SqlConfig(transactionMode = ISOLATED),
|
|
|
|
|
executionPhase = AFTER_TEST_METHOD))
|
|
|
|
|
fun userTest() {
|
|
|
|
|
// execute code that needs the test data to be committed
|
|
|
|
|
// to the database outside of the test's transaction
|
|
|
|
|
}
|
2019-08-30 08:35:53 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from
|
|
|
|
|
`Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
[[testcontext-executing-sql-declaratively-script-configuration]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Script Configuration with `@SqlConfig`
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
You can configure script parsing and error handling by using the `@SqlConfig` annotation.
|
|
|
|
|
When declared as a class-level annotation on an integration test class, `@SqlConfig`
|
|
|
|
|
serves as global configuration for all SQL scripts within the test class hierarchy. When
|
|
|
|
|
declared directly by using the `config` attribute of the `@Sql` annotation, `@SqlConfig`
|
|
|
|
|
serves as local configuration for the SQL scripts declared within the enclosing `@Sql`
|
|
|
|
|
annotation. Every attribute in `@SqlConfig` has an implicit default value, which is
|
2018-10-25 21:15:58 +08:00
|
|
|
|
documented in the javadoc of the corresponding attribute. Due to the rules defined for
|
2018-09-18 22:42:09 +08:00
|
|
|
|
annotation attributes in the Java Language Specification, it is, unfortunately, not
|
|
|
|
|
possible to assign a value of `null` to an annotation attribute. Thus, in order to
|
|
|
|
|
support overrides of inherited global configuration, `@SqlConfig` attributes have an
|
2019-07-23 21:23:44 +08:00
|
|
|
|
explicit default value of either `""` (for Strings), `{}` (for arrays), or `DEFAULT` (for
|
|
|
|
|
enumerations). This approach lets local declarations of `@SqlConfig` selectively override
|
|
|
|
|
individual attributes from global declarations of `@SqlConfig` by providing a value other
|
|
|
|
|
than `""`, `{}`, or `DEFAULT`. Global `@SqlConfig` attributes are inherited whenever
|
|
|
|
|
local `@SqlConfig` attributes do not supply an explicit value other than `""`, `{}`, or
|
|
|
|
|
`DEFAULT`. Explicit local configuration, therefore, overrides global configuration.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
The configuration options provided by `@Sql` and `@SqlConfig` are equivalent to those
|
|
|
|
|
supported by `ScriptUtils` and `ResourceDatabasePopulator` but are a superset of those
|
2018-10-25 21:15:58 +08:00
|
|
|
|
provided by the `<jdbc:initialize-database/>` XML namespace element. See the javadoc of
|
2018-09-18 22:42:09 +08:00
|
|
|
|
individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql`] and
|
|
|
|
|
{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-executing-sql-declaratively-tx]]
|
|
|
|
|
*Transaction management for `@Sql`*
|
|
|
|
|
|
2018-09-18 21:38:06 +08:00
|
|
|
|
By default, the `SqlScriptsTestExecutionListener` infers the desired transaction
|
2018-08-30 23:29:17 +08:00
|
|
|
|
semantics for scripts configured by using `@Sql`. Specifically, SQL scripts are run
|
|
|
|
|
without a transaction, within an existing Spring-managed transaction (for example, a
|
2015-03-03 18:38:01 +08:00
|
|
|
|
transaction managed by the `TransactionalTestExecutionListener` for a test annotated with
|
2018-09-18 21:43:14 +08:00
|
|
|
|
`@Transactional`), or within an isolated transaction, depending on the configured value
|
2015-03-03 18:38:01 +08:00
|
|
|
|
of the `transactionMode` attribute in `@SqlConfig` and the presence of a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`PlatformTransactionManager` in the test's `ApplicationContext`. As a bare minimum,
|
2015-03-03 18:38:01 +08:00
|
|
|
|
however, a `javax.sql.DataSource` must be present in the test's `ApplicationContext`.
|
|
|
|
|
|
|
|
|
|
If the algorithms used by `SqlScriptsTestExecutionListener` to detect a `DataSource` and
|
|
|
|
|
`PlatformTransactionManager` and infer the transaction semantics do not suit your needs,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
you can specify explicit names by setting the `dataSource` and `transactionManager`
|
|
|
|
|
attributes of `@SqlConfig`. Furthermore, you can control the transaction propagation
|
|
|
|
|
behavior by setting the `transactionMode` attribute of `@SqlConfig` (for example, whether
|
|
|
|
|
scripts should be run in an isolated transaction). Although a thorough discussion of all
|
|
|
|
|
supported options for transaction management with `@Sql` is beyond the scope of this
|
2018-10-25 21:15:58 +08:00
|
|
|
|
reference manual, the javadoc for
|
2018-09-18 22:42:09 +08:00
|
|
|
|
{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] and
|
|
|
|
|
{api-spring-framework}/test/context/jdbc/SqlScriptsTestExecutionListener.html[`SqlScriptsTestExecutionListener`]
|
|
|
|
|
provide detailed information, and the following example shows a typical testing scenario
|
2018-08-30 23:29:17 +08:00
|
|
|
|
that uses JUnit Jupiter and transactional tests with `@Sql`:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2017-09-28 05:27:44 +08:00
|
|
|
|
@SpringJUnitConfig(TestDatabaseConfig.class)
|
2015-03-03 18:38:01 +08:00
|
|
|
|
@Transactional
|
2017-09-28 05:27:44 +08:00
|
|
|
|
class TransactionalSqlScriptsTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-09-28 05:27:44 +08:00
|
|
|
|
final JdbcTemplate jdbcTemplate;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired
|
2017-09-28 05:27:44 +08:00
|
|
|
|
TransactionalSqlScriptsTests(DataSource dataSource) {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
2016-08-26 19:57:18 +08:00
|
|
|
|
@Sql("/test-data.sql")
|
2017-09-28 05:27:44 +08:00
|
|
|
|
void usersTest() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
// verify state in test database:
|
|
|
|
|
assertNumUsers(2);
|
|
|
|
|
// execute code that uses the test data...
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-28 05:27:44 +08:00
|
|
|
|
int countRowsInTable(String tableName) {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-28 05:27:44 +08:00
|
|
|
|
void assertNumUsers(int expected) {
|
|
|
|
|
assertEquals(expected, countRowsInTable("user"),
|
|
|
|
|
"Number of rows in the [user] table.");
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestDatabaseConfig::class)
|
|
|
|
|
@Transactional
|
|
|
|
|
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {
|
|
|
|
|
|
|
|
|
|
val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Sql("/test-data.sql")
|
|
|
|
|
fun usersTest() {
|
|
|
|
|
// verify state in test database:
|
|
|
|
|
assertNumUsers(2)
|
|
|
|
|
// execute code that uses the test data...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun countRowsInTable(tableName: String): Int {
|
|
|
|
|
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun assertNumUsers(expected: Int) {
|
|
|
|
|
assertEquals(expected, countRowsInTable("user"),
|
|
|
|
|
"Number of rows in the [user] table.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Note that there is no need to clean up the database after the `usersTest()` method is
|
|
|
|
|
run, since any changes made to the database (either within the test method or within the
|
|
|
|
|
`/test-data.sql` script) are automatically rolled back by the
|
|
|
|
|
`TransactionalTestExecutionListener` (see <<testcontext-tx,transaction management>> for
|
|
|
|
|
details).
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-07-21 21:37:51 +08:00
|
|
|
|
[[testcontext-executing-sql-declaratively-script-merging]]
|
|
|
|
|
====== Merging and Overriding Configuration with `@SqlMergeMode`
|
|
|
|
|
|
|
|
|
|
As of Spring Framework 5.2, it is possible to merge method-level `@Sql` declarations with
|
|
|
|
|
class-level declarations. For example, this allows you to provide the configuration for a
|
|
|
|
|
database schema or some common test data once per test class and then provide additional,
|
|
|
|
|
use case specific test data per test method. To enable `@Sql` merging, annotate either
|
|
|
|
|
your test class or test method with `@SqlMergeMode(MERGE)`. To disable merging for a
|
|
|
|
|
specific test method (or specific test subclass), you can switch back to the default mode
|
|
|
|
|
via `@SqlMergeMode(OVERRIDE)`. Consult the <<spring-testing-annotation-sqlmergemode,
|
|
|
|
|
`@SqlMergeMode` annotation documentation section>> for examples and further details.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2016-09-06 01:03:26 +08:00
|
|
|
|
[[testcontext-parallel-test-execution]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== Parallel Test Execution
|
2016-09-06 00:58:39 +08:00
|
|
|
|
|
2019-09-26 21:02:30 +08:00
|
|
|
|
Spring Framework 5.0 introduced basic support for executing tests in parallel within a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
single JVM when using the Spring TestContext Framework. In general, this means that most
|
2016-09-06 00:58:39 +08:00
|
|
|
|
test classes or test methods can be executed in parallel without any changes to test code
|
|
|
|
|
or configuration.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
TIP: For details on how to set up parallel test execution, see the documentation for your
|
2016-09-06 00:58:39 +08:00
|
|
|
|
testing framework, build tool, or IDE.
|
|
|
|
|
|
|
|
|
|
Keep in mind that the introduction of concurrency into your test suite can result in
|
2018-09-18 22:42:09 +08:00
|
|
|
|
unexpected side effects, strange runtime behavior, and tests that fail intermittently or
|
|
|
|
|
seemingly randomly. The Spring Team therefore provides the following general guidelines
|
2018-08-30 23:29:17 +08:00
|
|
|
|
for when not to execute tests in parallel.
|
2016-09-06 00:58:39 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Do not execute tests in parallel if the tests:
|
2016-09-06 00:58:39 +08:00
|
|
|
|
|
2019-09-26 21:02:30 +08:00
|
|
|
|
* Use Spring Framework's `@DirtiesContext` support.
|
|
|
|
|
* Use Spring Boot's `@MockBean` or `@SpyBean` support.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* Use JUnit 4's `@FixMethodOrder` support or any testing framework feature
|
|
|
|
|
that is designed to ensure that test methods run in a particular order. Note,
|
2016-09-06 00:58:39 +08:00
|
|
|
|
however, that this does not apply if entire test classes are executed in parallel.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* Change the state of shared services or systems such as a database, message broker,
|
2019-09-26 21:02:30 +08:00
|
|
|
|
filesystem, and others. This applies to both embedded and external systems.
|
2016-09-06 00:58:39 +08:00
|
|
|
|
|
|
|
|
|
[TIP]
|
|
|
|
|
====
|
|
|
|
|
If parallel test execution fails with an exception stating that the `ApplicationContext`
|
|
|
|
|
for the current test is no longer active, this typically means that the
|
|
|
|
|
`ApplicationContext` was removed from the `ContextCache` in a different thread.
|
|
|
|
|
|
|
|
|
|
This may be due to the use of `@DirtiesContext` or due to automatic eviction from the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ContextCache`. If `@DirtiesContext` is the culprit, you either need to find a way to
|
|
|
|
|
avoid using `@DirtiesContext` or exclude such tests from parallel execution. If the
|
2016-09-06 00:58:39 +08:00
|
|
|
|
maximum size of the `ContextCache` has been exceeded, you can increase the maximum size
|
2019-03-05 20:08:34 +08:00
|
|
|
|
of the cache. See the discussion on <<testcontext-ctx-management-caching, context caching>>
|
|
|
|
|
for details.
|
2016-09-06 00:58:39 +08:00
|
|
|
|
====
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
WARNING: Parallel test execution in the Spring TestContext Framework is only possible if
|
|
|
|
|
the underlying `TestContext` implementation provides a copy constructor, as explained in
|
2018-10-25 21:15:58 +08:00
|
|
|
|
the javadoc for {api-spring-framework}/test/context/TestContext.html[`TestContext`]. The
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`DefaultTestContext` used in Spring provides such a constructor. However, if you use a
|
|
|
|
|
third-party library that provides a custom `TestContext` implementation, you need to
|
|
|
|
|
verify that it is suitable for parallel test execution.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2016-09-06 00:58:39 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-support-classes]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
==== TestContext Framework Support Classes
|
|
|
|
|
|
|
|
|
|
This section describes the various classes that support the Spring TestContext Framework.
|
|
|
|
|
|
2015-07-30 04:03:44 +08:00
|
|
|
|
[[testcontext-junit4-runner]]
|
2016-05-04 00:45:47 +08:00
|
|
|
|
===== Spring JUnit 4 Runner
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
|
|
|
|
|
runner (supported on JUnit 4.12 or higher). By annotating test classes with
|
2016-02-28 06:02:55 +08:00
|
|
|
|
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
|
2018-08-30 23:29:17 +08:00
|
|
|
|
variant, developers can implement standard JUnit 4-based unit and integration tests and
|
2018-09-18 22:42:09 +08:00
|
|
|
|
simultaneously reap the benefits of the TestContext framework, such as support for
|
|
|
|
|
loading application contexts, dependency injection of test instances, transactional test
|
|
|
|
|
method execution, and so on. If you want to use the Spring TestContext Framework with an
|
|
|
|
|
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
|
|
|
|
|
(such as the `MockitoJUnitRunner`), you can, optionally, use
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<testcontext-junit4-rules, Spring's support for JUnit rules>> instead.
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following code listing shows the minimal requirements for configuring a test class to
|
|
|
|
|
run with the custom Spring `Runner`:
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-07-30 04:03:44 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@RunWith(SpringRunner.class)
|
|
|
|
|
@TestExecutionListeners({})
|
|
|
|
|
public class SimpleTest {
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Test
|
|
|
|
|
public void testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-07-30 04:03:44 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@RunWith(SpringRunner::class)
|
|
|
|
|
@TestExecutionListeners
|
|
|
|
|
class SimpleTest {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
|
|
|
|
|
disable the default listeners, which otherwise would require an `ApplicationContext` to
|
|
|
|
|
be configured through `@ContextConfiguration`.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-07-30 04:03:44 +08:00
|
|
|
|
[[testcontext-junit4-rules]]
|
2016-05-04 00:45:47 +08:00
|
|
|
|
===== Spring JUnit 4 Rules
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
|
|
|
|
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
|
2018-08-30 23:29:17 +08:00
|
|
|
|
4 rules (supported on JUnit 4.12 or higher):
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
|
|
|
|
* `SpringClassRule`
|
|
|
|
|
* `SpringMethodRule`
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
|
|
|
|
|
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
|
|
|
|
|
instance-level and method-level features of the Spring TestContext Framework.
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
|
|
|
|
|
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
|
|
|
|
|
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
|
|
|
|
|
third-party runners (such as the `MockitoJUnitRunner`).
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
To support the full functionality of the TestContext framework, you must combine a
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
|
|
|
|
|
to declare these rules in an integration test:
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-07-30 04:03:44 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// Optionally specify a non-Spring Runner via @RunWith(...)
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
public class IntegrationTest {
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@ClassRule
|
|
|
|
|
public static final SpringClassRule springClassRule = new SpringClassRule();
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Rule
|
|
|
|
|
public final SpringMethodRule springMethodRule = new SpringMethodRule();
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Test
|
|
|
|
|
public void testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-07-30 04:03:44 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Optionally specify a non-Spring Runner via @RunWith(...)
|
|
|
|
|
@ContextConfiguration
|
|
|
|
|
class IntegrationTest {
|
|
|
|
|
|
|
|
|
|
@Rule
|
|
|
|
|
val springMethodRule = SpringMethodRule()
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
@ClassRule
|
|
|
|
|
val springClassRule = SpringClassRule()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-support-classes-junit4]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== JUnit 4 Support Classes
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The `org.springframework.test.context.junit4` package provides the following support
|
2018-08-30 23:29:17 +08:00
|
|
|
|
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
* `AbstractJUnit4SpringContextTests`
|
|
|
|
|
* `AbstractTransactionalJUnit4SpringContextTests`
|
|
|
|
|
|
|
|
|
|
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
|
|
|
|
|
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
|
|
|
|
|
`protected` `applicationContext` instance variable that you can use to perform explicit
|
|
|
|
|
bean lookups or to test the state of the context as a whole.
|
|
|
|
|
|
|
|
|
|
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
|
|
|
|
|
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
|
|
|
|
|
access. This class expects a `javax.sql.DataSource` bean and a
|
|
|
|
|
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
|
|
|
|
|
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
|
|
|
|
|
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
|
|
|
|
|
database. You can use such queries to confirm database state both before and after
|
|
|
|
|
running 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
|
2019-03-05 20:08:34 +08:00
|
|
|
|
an ORM tool, be sure to avoid <<testcontext-tx-false-positives, false positives>>.
|
|
|
|
|
As mentioned in <<integration-testing-support-jdbc>>,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
|
|
|
|
|
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
|
|
|
|
|
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
|
|
|
|
|
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
|
|
|
|
|
|
|
|
|
|
TIP: These classes are a convenience for extension. If you do not want your test classes
|
|
|
|
|
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
|
2019-03-05 20:08:34 +08:00
|
|
|
|
classes by using `@RunWith(SpringRunner.class)` or <<testcontext-junit4-rules, Spring's
|
2015-07-30 04:03:44 +08:00
|
|
|
|
JUnit rules>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2017-09-28 05:01:59 +08:00
|
|
|
|
[[testcontext-junit-jupiter-extension]]
|
|
|
|
|
===== SpringExtension for JUnit Jupiter
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The Spring TestContext Framework offers full integration with the JUnit Jupiter testing
|
|
|
|
|
framework, introduced in JUnit 5. By annotating test classes with
|
|
|
|
|
`@ExtendWith(SpringExtension.class)`, you can implement standard JUnit Jupiter-based unit
|
|
|
|
|
and integration tests and simultaneously reap the benefits of the TestContext framework,
|
|
|
|
|
such as support for loading application contexts, dependency injection of test instances,
|
|
|
|
|
transactional test method execution, and so on.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-01-09 22:39:56 +08:00
|
|
|
|
Furthermore, thanks to the rich extension API in JUnit Jupiter, Spring provides the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
following features above and beyond the feature set that Spring supports for JUnit 4 and
|
|
|
|
|
TestNG:
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
|
|
|
|
* Dependency injection for test constructors, test methods, and test lifecycle callback
|
2018-09-18 22:42:09 +08:00
|
|
|
|
methods. See <<testcontext-junit-jupiter-di>> for further details.
|
2019-03-21 06:48:14 +08:00
|
|
|
|
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
|
2018-09-18 22:42:09 +08:00
|
|
|
|
test execution] based on SpEL expressions, environment variables, system properties,
|
|
|
|
|
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
|
|
|
|
|
<<integration-testing-annotations-junit-jupiter>> for further details and examples.
|
|
|
|
|
* Custom composed annotations that combine annotations from Spring and JUnit Jupiter. See
|
|
|
|
|
the `@TransactionalDevTestConfig` and `@TransactionalIntegrationTest` examples in
|
|
|
|
|
<<integration-testing-annotations-meta>> for further details.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The following code listing shows how to configure a test class to use the
|
|
|
|
|
`SpringExtension` in conjunction with `@ContextConfiguration`:
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// Instructs JUnit Jupiter to extend the test with Spring support.
|
|
|
|
|
@ExtendWith(SpringExtension.class)
|
|
|
|
|
// Instructs Spring to load an ApplicationContext from TestConfig.class
|
|
|
|
|
@ContextConfiguration(classes = TestConfig.class)
|
|
|
|
|
class SimpleTests {
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Test
|
|
|
|
|
void testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Instructs JUnit Jupiter to extend the test with Spring support.
|
|
|
|
|
@ExtendWith(SpringExtension::class)
|
|
|
|
|
// Instructs Spring to load an ApplicationContext from TestConfig::class
|
|
|
|
|
@ContextConfiguration(classes = [TestConfig::class])
|
|
|
|
|
class SimpleTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-01-09 22:39:56 +08:00
|
|
|
|
Since you can also use annotations in JUnit 5 as meta-annotations, Spring provides the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`@SpringJUnitConfig` and `@SpringJUnitWebConfig` composed annotations to simplify the
|
|
|
|
|
configuration of the test `ApplicationContext` and JUnit Jupiter.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following example uses `@SpringJUnitConfig` to reduce the amount of configuration
|
|
|
|
|
used in the previous example:
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// Instructs Spring to register the SpringExtension with JUnit
|
|
|
|
|
// Jupiter and load an ApplicationContext from TestConfig.class
|
|
|
|
|
@SpringJUnitConfig(TestConfig.class)
|
|
|
|
|
class SimpleTests {
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Test
|
|
|
|
|
void testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Instructs Spring to register the SpringExtension with JUnit
|
|
|
|
|
// Jupiter and load an ApplicationContext from TestConfig.class
|
|
|
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
|
|
|
class SimpleTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2017-09-28 05:01:59 +08:00
|
|
|
|
Similarly, the following example uses `@SpringJUnitWebConfig` to create a
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`WebApplicationContext` for use with JUnit Jupiter:
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// Instructs Spring to register the SpringExtension with JUnit
|
|
|
|
|
// Jupiter and load a WebApplicationContext from TestWebConfig.class
|
|
|
|
|
@SpringJUnitWebConfig(TestWebConfig.class)
|
|
|
|
|
class SimpleWebTests {
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Test
|
|
|
|
|
void testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Instructs Spring to register the SpringExtension with JUnit
|
|
|
|
|
// Jupiter and load a WebApplicationContext from TestWebConfig::class
|
|
|
|
|
@SpringJUnitWebConfig(TestWebConfig::class)
|
|
|
|
|
class SimpleWebTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun testMethod() {
|
|
|
|
|
// execute test logic...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2017-09-28 05:01:59 +08:00
|
|
|
|
See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in
|
|
|
|
|
<<integration-testing-annotations-junit-jupiter>> for further details.
|
|
|
|
|
|
|
|
|
|
[[testcontext-junit-jupiter-di]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== Dependency Injection with `SpringExtension`
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`SpringExtension` implements the
|
2019-03-21 06:48:14 +08:00
|
|
|
|
link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
|
2018-09-18 22:42:09 +08:00
|
|
|
|
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
|
|
|
|
|
constructors, test methods, and test lifecycle callback methods.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Specifically, `SpringExtension` can inject dependencies from the test's
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`ApplicationContext` into test constructors and methods that are annotated with
|
|
|
|
|
`@BeforeAll`, `@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`,
|
|
|
|
|
`@ParameterizedTest`, and others.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2017-09-28 05:01:59 +08:00
|
|
|
|
[[testcontext-junit-jupiter-di-constructor]]
|
|
|
|
|
====== Constructor Injection
|
|
|
|
|
|
2019-05-09 22:07:21 +08:00
|
|
|
|
If a specific parameter in a constructor for a JUnit Jupiter test class is of type
|
2017-09-28 05:01:59 +08:00
|
|
|
|
`ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific
|
2019-05-09 22:07:21 +08:00
|
|
|
|
parameter with the corresponding bean or value from the test's `ApplicationContext`.
|
|
|
|
|
|
|
|
|
|
Spring can also be configured to autowire all arguments for a test class constructor if
|
|
|
|
|
the constructor is considered to be _autowirable_. A constructor is considered to be
|
|
|
|
|
autowirable if one of the following conditions is met (in order of precedence).
|
|
|
|
|
|
|
|
|
|
* The constructor is annotated with `@Autowired`.
|
2019-07-12 00:00:52 +08:00
|
|
|
|
* `@TestConstructor` is present or meta-present on the test class with the `autowireMode`
|
|
|
|
|
attribute set to `ALL`.
|
|
|
|
|
* The default _test constructor autowire mode_ has been changed to `ALL`.
|
2019-05-09 22:07:21 +08:00
|
|
|
|
|
|
|
|
|
See <<integration-testing-annotations-testconstructor>> for details on the use of
|
2019-07-12 00:00:52 +08:00
|
|
|
|
`@TestConstructor` and how to change the global _test constructor autowire mode_.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-05-09 22:07:21 +08:00
|
|
|
|
WARNING: If the constructor for a test class is considered to be _autowirable_, Spring
|
|
|
|
|
assumes the responsibility for resolving arguments for all parameters in the constructor.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Consequently, no other `ParameterResolver` registered with JUnit Jupiter can resolve
|
|
|
|
|
parameters for such a constructor.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-01-09 22:39:56 +08:00
|
|
|
|
[WARNING]
|
|
|
|
|
====
|
|
|
|
|
Constructor injection for test classes must not be used in conjunction with JUnit
|
|
|
|
|
Jupiter's `@TestInstance(PER_CLASS)` support if `@DirtiesContext` is used to close the
|
|
|
|
|
test's `ApplicationContext` before or after test methods.
|
|
|
|
|
|
|
|
|
|
The reason is that `@TestInstance(PER_CLASS)` instructs JUnit Jupiter to cache the test
|
|
|
|
|
instance between test method invocations. Consequently, the test instance will retain
|
|
|
|
|
references to beans that were originally injected from an `ApplicationContext` that has
|
|
|
|
|
been subsequently closed. Since the constructor for the test class will only be invoked
|
|
|
|
|
once in such scenarios, dependency injection will not occur again, and subsequent tests
|
|
|
|
|
will interact with beans from the closed `ApplicationContext` which may result in errors.
|
|
|
|
|
|
|
|
|
|
To use `@DirtiesContext` with "before test method" or "after test method" modes in
|
|
|
|
|
conjunction with `@TestInstance(PER_CLASS)`, one must configure dependencies from Spring
|
|
|
|
|
to be supplied via field or setter injection so that they can be re-injected between test
|
|
|
|
|
method invocations.
|
|
|
|
|
====
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In the following example, Spring injects the `OrderService` bean from the
|
2017-09-28 05:01:59 +08:00
|
|
|
|
`ApplicationContext` loaded from `TestConfig.class` into the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`OrderServiceIntegrationTests` constructor.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@SpringJUnitConfig(TestConfig.class)
|
|
|
|
|
class OrderServiceIntegrationTests {
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
private final OrderService orderService;
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Autowired
|
|
|
|
|
OrderServiceIntegrationTests(OrderService orderService) {
|
2019-08-30 08:35:53 +08:00
|
|
|
|
this.orderService = orderService;
|
2017-11-21 05:28:00 +08:00
|
|
|
|
}
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// tests that use the injected OrderService
|
|
|
|
|
}
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
|
|
|
class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){
|
|
|
|
|
// tests that use the injected OrderService
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Note that this feature lets test dependencies be `final` and therefore immutable.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-07-12 00:00:52 +08:00
|
|
|
|
If the `spring.test.constructor.autowire.mode` property is to `all` (see
|
2019-05-09 22:07:21 +08:00
|
|
|
|
<<integration-testing-annotations-testconstructor>>), we can omit the declaration of
|
2019-07-12 00:00:52 +08:00
|
|
|
|
`@Autowired` on the constructor in the previous example, resulting in the following.
|
2019-05-09 22:07:21 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2019-05-09 22:07:21 +08:00
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig.class)
|
|
|
|
|
class OrderServiceIntegrationTests {
|
|
|
|
|
|
|
|
|
|
private final OrderService orderService;
|
|
|
|
|
|
|
|
|
|
OrderServiceIntegrationTests(OrderService orderService) {
|
2019-08-30 08:35:53 +08:00
|
|
|
|
this.orderService = orderService;
|
2019-05-09 22:07:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tests that use the injected OrderService
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
|
|
|
class OrderServiceIntegrationTests(val orderService:OrderService) {
|
|
|
|
|
// tests that use the injected OrderService
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2017-09-28 05:01:59 +08:00
|
|
|
|
[[testcontext-junit-jupiter-di-method]]
|
|
|
|
|
====== Method Injection
|
|
|
|
|
|
|
|
|
|
If a parameter in a JUnit Jupiter test method or test lifecycle callback method is of
|
|
|
|
|
type `ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific
|
2017-09-28 05:01:59 +08:00
|
|
|
|
parameter with the corresponding bean from the test's `ApplicationContext`.
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In the following example, Spring injects the `OrderService` from the `ApplicationContext`
|
|
|
|
|
loaded from `TestConfig.class` into the `deleteOrder()` test method:
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@SpringJUnitConfig(TestConfig.class)
|
|
|
|
|
class OrderServiceIntegrationTests {
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Test
|
|
|
|
|
void deleteOrder(@Autowired OrderService orderService) {
|
|
|
|
|
// use orderService from the test's ApplicationContext
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
|
|
|
class OrderServiceIntegrationTests {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun deleteOrder(@Autowired orderService: OrderService) {
|
|
|
|
|
// use orderService from the test's ApplicationContext
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Due to the robustness of the `ParameterResolver` support in JUnit Jupiter, you can also
|
2018-09-18 22:42:09 +08:00
|
|
|
|
have multiple dependencies injected into a single method, not only from Spring but also
|
|
|
|
|
from JUnit Jupiter itself or other third-party extensions.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The following example shows how to have both Spring and JUnit Jupiter inject dependencies
|
|
|
|
|
into the `placeOrderRepeatedly()` test method simultaneously.
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@SpringJUnitConfig(TestConfig.class)
|
|
|
|
|
class OrderServiceIntegrationTests {
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@RepeatedTest(10)
|
|
|
|
|
void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
|
|
|
|
|
@Autowired OrderService orderService) {
|
2017-09-28 05:01:59 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// use orderService from the test's ApplicationContext
|
|
|
|
|
// and repetitionInfo from JUnit Jupiter
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-28 05:01:59 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitConfig(TestConfig::class)
|
|
|
|
|
class OrderServiceIntegrationTests {
|
|
|
|
|
|
|
|
|
|
@RepeatedTest(10)
|
|
|
|
|
fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) {
|
|
|
|
|
|
|
|
|
|
// use orderService from the test's ApplicationContext
|
|
|
|
|
// and repetitionInfo from JUnit Jupiter
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Note that the use of `@RepeatedTest` from JUnit Jupiter lets the test method gain access
|
|
|
|
|
to the `RepetitionInfo`.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testcontext-support-classes-testng]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
===== TestNG Support Classes
|
2015-07-30 04:03:44 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The `org.springframework.test.context.testng` package provides the following support
|
2018-08-30 23:29:17 +08:00
|
|
|
|
classes for TestNG based test cases:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
* `AbstractTestNGSpringContextTests`
|
|
|
|
|
* `AbstractTransactionalTestNGSpringContextTests`
|
|
|
|
|
|
|
|
|
|
`AbstractTestNGSpringContextTests` is an abstract base test class that integrates the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
|
|
|
|
|
TestNG environment. When you extend `AbstractTestNGSpringContextTests`, you can access a
|
|
|
|
|
`protected` `applicationContext` instance variable that you can use to perform explicit
|
|
|
|
|
bean lookups or to test the state of the context as a whole.
|
|
|
|
|
|
|
|
|
|
`AbstractTransactionalTestNGSpringContextTests` is an abstract transactional extension of
|
|
|
|
|
`AbstractTestNGSpringContextTests` that adds some convenience functionality for JDBC
|
|
|
|
|
access. This class expects a `javax.sql.DataSource` bean and a
|
|
|
|
|
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
|
|
|
|
|
extend `AbstractTransactionalTestNGSpringContextTests`, you can access a `protected`
|
|
|
|
|
`jdbcTemplate` instance variable that you can use to execute SQL statements to query the
|
|
|
|
|
database. You can use such queries to confirm database state both before and after
|
|
|
|
|
running 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
|
2019-03-05 20:08:34 +08:00
|
|
|
|
an ORM tool, be sure to avoid <<testcontext-tx-false-positives, false positives>>.
|
|
|
|
|
As mentioned in <<integration-testing-support-jdbc>>,
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`AbstractTransactionalTestNGSpringContextTests` also provides convenience methods that
|
|
|
|
|
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
|
|
|
|
|
Furthermore, `AbstractTransactionalTestNGSpringContextTests` provides an
|
|
|
|
|
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
|
|
|
|
|
|
|
|
|
|
TIP: These classes are a convenience for extension. If you do not want your test classes
|
|
|
|
|
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
|
|
|
|
|
classes by using `@ContextConfiguration`, `@TestExecutionListeners`, and so on and by
|
|
|
|
|
manually instrumenting your test class with a `TestContextManager`. See the source code
|
|
|
|
|
of `AbstractTestNGSpringContextTests` for an example of how to instrument your test class.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-framework]]
|
|
|
|
|
=== Spring MVC Test Framework
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The Spring MVC Test framework provides first class support for testing Spring MVC code
|
|
|
|
|
with a fluent API that you can use with JUnit, TestNG, or any other testing framework. It
|
|
|
|
|
is built on the {api-spring-framework}/mock/web/package-summary.html[Servlet API mock objects]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
from the `spring-test` module and, hence, does not use a running Servlet container. It
|
2018-09-18 22:42:09 +08:00
|
|
|
|
uses the `DispatcherServlet` to provide full Spring MVC runtime behavior and provides
|
|
|
|
|
support for loading actual Spring configuration with the TestContext framework in
|
|
|
|
|
addition to a standalone mode, in which you can manually instantiate controllers and test
|
|
|
|
|
them one at a time.
|
2015-07-07 10:27:19 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Spring MVC Test also provides client-side support for testing code that uses the
|
|
|
|
|
`RestTemplate`. Client-side tests mock the server responses and also do not use a running
|
|
|
|
|
server.
|
2015-07-07 10:27:19 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
TIP: Spring Boot provides an option to write full, end-to-end integration tests that
|
|
|
|
|
include a running server. If this is your goal, see the
|
2019-10-23 03:00:06 +08:00
|
|
|
|
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing[Spring Boot Reference Guide].
|
2015-10-08 22:57:31 +08:00
|
|
|
|
For more information on the differences between out-of-container and end-to-end
|
|
|
|
|
integration tests, see <<spring-mvc-test-vs-end-to-end-integration-tests>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-07-07 11:06:40 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[spring-mvc-test-server]]
|
|
|
|
|
==== Server-Side Tests
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
You can write a plain unit test for a Spring MVC controller by using JUnit or TestNG. To
|
|
|
|
|
do so, instantiate the controller, inject it with mocked or stubbed dependencies, and
|
|
|
|
|
call its methods (passing `MockHttpServletRequest`, `MockHttpServletResponse`, and
|
|
|
|
|
others, as necessary). However, when writing such a unit test, much remains untested: for
|
|
|
|
|
example, request mappings, data binding, type conversion, validation, and much more.
|
|
|
|
|
Furthermore, other controller methods such as `@InitBinder`, `@ModelAttribute`, and
|
|
|
|
|
`@ExceptionHandler` may also be invoked as part of the request processing lifecycle.
|
2015-07-07 10:27:19 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The goal of Spring MVC Test is to provide an effective way to test controllers by
|
|
|
|
|
performing requests and generating responses through the actual `DispatcherServlet`.
|
2015-07-07 10:27:19 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
Spring MVC Test builds on the familiar <<mock-objects-servlet, "`mock`" implementations of
|
2018-09-18 22:42:09 +08:00
|
|
|
|
the Servlet API>> available in the `spring-test` module. This allows performing requests
|
|
|
|
|
and generating responses without the need for running in a Servlet container. For the
|
|
|
|
|
most part, everything should work as it does at runtime with a few notable exceptions, as
|
|
|
|
|
explained in <<spring-mvc-test-vs-end-to-end-integration-tests>>. The following JUnit
|
|
|
|
|
Jupiter-based example uses Spring MVC Test:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
|
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
|
|
|
|
|
class ExampleTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
MockMvc mockMvc;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup(WebApplicationContext wac) {
|
|
|
|
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
|
|
|
|
|
}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
@Test
|
|
|
|
|
void getAccount() throws Exception {
|
|
|
|
|
this.mockMvc.perform(get("/accounts/1")
|
2019-08-30 08:35:53 +08:00
|
|
|
|
.accept(MediaType.APPLICATION_JSON))
|
2017-11-21 05:28:00 +08:00
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(content().contentType("application/json"))
|
|
|
|
|
.andExpect(jsonPath("$.name").value("Lee"));
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
import org.springframework.test.web.servlet.get
|
|
|
|
|
|
|
|
|
|
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
|
|
|
|
|
class ExampleTests {
|
|
|
|
|
|
|
|
|
|
lateinit var mockMvc: MockMvc
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup(wac: WebApplicationContext) {
|
|
|
|
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun getAccount() {
|
|
|
|
|
mockMvc.get("/accounts/1") {
|
|
|
|
|
accept = MediaType.APPLICATION_JSON
|
|
|
|
|
}.andExpect {
|
|
|
|
|
status { isOk }
|
|
|
|
|
content { contentType(MediaType.APPLICATION_JSON) }
|
|
|
|
|
jsonPath("$.name") { value("Lee") }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
NOTE: A dedicated <<languages.adoc#mockmvc-dsl, MockMvc DSL>> is available in Kotlin
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The preceding test relies on the `WebApplicationContext` support of the TestContext
|
|
|
|
|
framework to load Spring configuration from an XML configuration file located in the same
|
|
|
|
|
package as the test class, but Java-based and Groovy-based configuration are also
|
|
|
|
|
supported. See these
|
2015-07-07 10:27:19 +08:00
|
|
|
|
https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context[sample tests].
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
The `MockMvc` instance is used to perform a `GET` request to `/accounts/1` and verify
|
2018-09-18 22:42:09 +08:00
|
|
|
|
that the resulting response has status 200, the content type is `application/json`, and
|
|
|
|
|
the response body has a JSON property called `name` with the value `Lee`. The `jsonPath`
|
2015-10-08 22:57:31 +08:00
|
|
|
|
syntax is supported through the Jayway https://github.com/jayway/JsonPath[JsonPath
|
2018-09-18 22:42:09 +08:00
|
|
|
|
project]. Many other options for verifying the result of the performed request are
|
|
|
|
|
discussed later in this document.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[spring-mvc-test-server-static-imports]]
|
|
|
|
|
===== Static Imports
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
The fluent API in the example from the <<spring-mvc-test-server, preceding section>>
|
2018-09-18 22:42:09 +08:00
|
|
|
|
requires a few static imports, such as `MockMvcRequestBuilders.{asterisk}`,
|
|
|
|
|
`MockMvcResultMatchers.{asterisk}`, and `MockMvcBuilders.{asterisk}`. An easy way to find
|
|
|
|
|
these classes is to search for types that match `MockMvc*`. If you use Eclipse or the
|
2020-02-29 01:15:56 +08:00
|
|
|
|
https://spring.io/tools[Spring Tools for Eclipse], be sure to add them as "`favorite static members`" in
|
2018-09-18 22:42:09 +08:00
|
|
|
|
the Eclipse preferences under Java -> Editor -> Content Assist -> Favorites. Doing so
|
|
|
|
|
lets you use content assist after typing the first character of the static method name.
|
|
|
|
|
Other IDEs (such as IntelliJ) may not require any additional configuration. Check the
|
|
|
|
|
support for code completion on static members.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-setup-options]]
|
2017-01-26 01:32:07 +08:00
|
|
|
|
===== Setup Choices
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
You have two main options for creating an instance of `MockMvc`. The first is to load
|
|
|
|
|
Spring MVC configuration through the TestContext framework, which loads the Spring
|
|
|
|
|
configuration and injects a `WebApplicationContext` into the test to use to build a
|
|
|
|
|
`MockMvc` instance. The following example shows how to do so:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
|
|
|
|
|
class MyWebTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
MockMvc mockMvc;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup(WebApplicationContext wac) {
|
2019-08-30 08:35:53 +08:00
|
|
|
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
|
|
|
|
|
class MyWebTests {
|
|
|
|
|
|
|
|
|
|
lateinit var mockMvc: MockMvc
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup(wac: WebApplicationContext) {
|
|
|
|
|
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Your second option is to manually create a controller instance without loading Spring
|
2018-09-18 22:42:09 +08:00
|
|
|
|
configuration. Instead, basic default configuration, roughly comparable to that of the
|
|
|
|
|
MVC JavaConfig or the MVC namespace, is automatically created. You can customize it to a
|
|
|
|
|
degree. The following example shows how to do so:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyWebTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
MockMvc mockMvc;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
class MyWebTests {
|
|
|
|
|
|
|
|
|
|
lateinit var mockMvc : MockMvc
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup() {
|
|
|
|
|
mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-07-07 10:27:19 +08:00
|
|
|
|
Which setup option should you use?
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The `webAppContextSetup` loads your actual Spring MVC configuration, resulting in a more
|
|
|
|
|
complete integration test. Since the TestContext framework caches the loaded Spring
|
|
|
|
|
configuration, it helps keep tests running fast, even as you introduce more tests in your
|
|
|
|
|
test suite. Furthermore, you can inject mock services into controllers through Spring
|
|
|
|
|
configuration to remain focused on testing the web layer. The following example declares
|
|
|
|
|
a mock service with Mockito:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
|
|
|
|
|
<constructor-arg value="org.example.AccountService"/>
|
|
|
|
|
</bean>
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can then inject the mock service into the test to set up and verify your
|
|
|
|
|
expectations, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
|
|
|
|
|
class AccountTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
@Autowired
|
2019-08-21 21:54:28 +08:00
|
|
|
|
AccountService accountService;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
MockMvc mockMvc;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup(WebApplicationContext wac) {
|
|
|
|
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
|
|
|
|
|
}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
|
|
|
|
|
class AccountTests {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
lateinit var accountService: AccountService
|
|
|
|
|
|
|
|
|
|
lateinit mockMvc: MockMvc
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup(wac: WebApplicationContext) {
|
|
|
|
|
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The `standaloneSetup`, on the other hand, is a little closer to a unit test. It tests one
|
|
|
|
|
controller at a time. You can manually inject the controller with mock dependencies, and
|
|
|
|
|
it does not involve loading Spring configuration. Such tests are more focused on style
|
2015-08-26 21:13:19 +08:00
|
|
|
|
and make it easier to see which controller is being tested, whether any specific Spring
|
2018-08-30 23:29:17 +08:00
|
|
|
|
MVC configuration is required to work, and so on. The `standaloneSetup` is also a very
|
2015-08-26 21:13:19 +08:00
|
|
|
|
convenient way to write ad-hoc tests to verify specific behavior or to debug an issue.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
As with most "`integration versus unit testing`" debates, there is no right or wrong
|
|
|
|
|
answer. However, using the `standaloneSetup` does imply the need for additional
|
|
|
|
|
`webAppContextSetup` tests in order to verify your Spring MVC configuration.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Alternatively, you can write all your tests with `webAppContextSetup`, in order to always
|
|
|
|
|
test against your actual Spring MVC configuration.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-01-26 01:32:07 +08:00
|
|
|
|
[[spring-mvc-test-server-setup-steps]]
|
|
|
|
|
===== Setup Features
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
No matter which MockMvc builder you use, all `MockMvcBuilder` implementations provide
|
2018-09-18 22:42:09 +08:00
|
|
|
|
some common and very useful features. For example, you can declare an `Accept` header for
|
|
|
|
|
all requests and expect a status of 200 as well as a `Content-Type` header in all
|
|
|
|
|
responses, as follows:
|
2017-01-26 01:32:07 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-01-26 01:32:07 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
// static import of MockMvcBuilders.standaloneSetup
|
2017-01-26 01:32:07 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
MockMvc mockMvc = standaloneSetup(new MusicController())
|
2017-01-26 01:32:07 +08:00
|
|
|
|
.defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
|
|
|
|
|
.alwaysExpect(status().isOk())
|
|
|
|
|
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
|
|
|
|
|
.build();
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In addition, third-party frameworks (and applications) can pre-package setup
|
2018-09-18 22:42:09 +08:00
|
|
|
|
instructions, such as those in a `MockMvcConfigurer`. The Spring Framework has one such
|
|
|
|
|
built-in implementation that helps to save and re-use the HTTP session across requests.
|
|
|
|
|
You can use it as follows:
|
2017-01-26 01:32:07 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2017-01-26 01:32:07 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// static import of SharedHttpSessionConfigurer.sharedHttpSession
|
2017-01-26 01:32:07 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
|
|
|
|
|
.apply(sharedHttpSession())
|
|
|
|
|
.build();
|
2017-01-26 01:32:07 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// Use mockMvc to perform requests...
|
2017-01-26 01:32:07 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
|
|
|
|
|
----
|
|
|
|
|
|
2018-10-25 21:15:58 +08:00
|
|
|
|
See the javadoc for
|
|
|
|
|
{api-spring-framework}/test/web/servlet/setup/ConfigurableMockMvcBuilder.html[`ConfigurableMockMvcBuilder`]
|
|
|
|
|
for a list of all MockMvc builder features or use the IDE to explore the available options.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[spring-mvc-test-server-performing-requests]]
|
|
|
|
|
===== Performing Requests
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can perform requests that use any HTTP method, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
import org.springframework.test.web.servlet.post
|
|
|
|
|
|
|
|
|
|
mockMvc.post("/hotels/{id}", 42) {
|
|
|
|
|
accept = MediaType.APPLICATION_JSON
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-07-07 10:27:19 +08:00
|
|
|
|
You can also perform file upload requests that internally use
|
|
|
|
|
`MockMultipartHttpServletRequest` so that there is no actual parsing of a multipart
|
2018-08-30 23:29:17 +08:00
|
|
|
|
request. Rather, you have to set it up to be similar to the following example:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2017-01-26 06:04:25 +08:00
|
|
|
|
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
import org.springframework.test.web.servlet.multipart
|
|
|
|
|
|
|
|
|
|
mockMvc.multipart("/doc") {
|
|
|
|
|
file("a1", "ABC".toByteArray(charset("UTF8")))
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can specify query parameters in URI template style, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
mockMvc.get("/hotels?thing={thing}", "somewhere")
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
You can also add Servlet request parameters that represent either query or form
|
|
|
|
|
parameters, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
import org.springframework.test.web.servlet.get
|
|
|
|
|
|
|
|
|
|
mockMvc.get("/hotels") {
|
|
|
|
|
param("thing", "somewhere")
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
If application code relies on Servlet request parameters and does not check the query
|
|
|
|
|
string explicitly (as is most often the case), it does not matter which option you use.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Keep in mind, however, that query parameters provided with the URI template are decoded
|
|
|
|
|
while request parameters provided through the `param(...)` method are expected to already
|
|
|
|
|
be decoded.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In most cases, it is preferable to leave the context path and the Servlet path out of the
|
|
|
|
|
request URI. If you must test with the full request URI, be sure to set the `contextPath`
|
|
|
|
|
and `servletPath` accordingly so that request mappings work, as the following example
|
|
|
|
|
shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
import org.springframework.test.web.servlet.get
|
|
|
|
|
|
|
|
|
|
mockMvc.get("/app/main/hotels/{id}") {
|
|
|
|
|
contextPath = "/app"
|
|
|
|
|
servletPath = "/main"
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In the preceding example, it would be cumbersome to set the `contextPath` and
|
|
|
|
|
`servletPath` with every performed request. Instead, you can set up default request
|
|
|
|
|
properties, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
class MyWebTests {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
MockMvc mockMvc;
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup() {
|
2015-03-03 18:38:01 +08:00
|
|
|
|
mockMvc = standaloneSetup(new AccountController())
|
|
|
|
|
.defaultRequest(get("/")
|
|
|
|
|
.contextPath("/app").servletPath("/main")
|
2017-10-20 16:33:44 +08:00
|
|
|
|
.accept(MediaType.APPLICATION_JSON)).build();
|
2015-03-03 18:38:01 +08:00
|
|
|
|
}
|
2019-08-30 08:35:53 +08:00
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
The preceding properties affect every request performed through the `MockMvc` instance.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If the same property is also specified on a given request, it overrides the default
|
|
|
|
|
value. That is why the HTTP method and URI in the default request do not matter, since
|
|
|
|
|
they must be specified on every request.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-defining-expectations]]
|
|
|
|
|
===== Defining Expectations
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You can define expectations by appending one or more `.andExpect(..)` calls after
|
|
|
|
|
performing a request, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
import org.springframework.test.web.servlet.get
|
|
|
|
|
|
|
|
|
|
mockMvc.get("/accounts/1").andExpect {
|
|
|
|
|
status().isOk()
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-10-08 22:57:31 +08:00
|
|
|
|
`MockMvcResultMatchers.*` provides a number of expectations, some of which are further
|
2015-07-07 10:27:19 +08:00
|
|
|
|
nested with more detailed expectations.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2015-10-08 22:57:31 +08:00
|
|
|
|
Expectations fall in two general categories. The first category of assertions verifies
|
2018-09-18 22:42:09 +08:00
|
|
|
|
properties of the response (for example, the response status, headers, and content).
|
|
|
|
|
These are the most important results to assert.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The second category of assertions goes beyond the response. These assertions let you
|
|
|
|
|
inspect Spring MVC specific aspects, such as which controller method processed the
|
|
|
|
|
request, whether an exception was raised and handled, what the content of the model is,
|
|
|
|
|
what view was selected, what flash attributes were added, and so on. They also let you
|
|
|
|
|
inspect Servlet specific aspects, such as request and session attributes.
|
2015-10-08 22:57:31 +08:00
|
|
|
|
|
|
|
|
|
The following test asserts that binding or validation failed:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
mockMvc.perform(post("/persons"))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(model().attributeHasErrors("person"));
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
import org.springframework.test.web.servlet.post
|
|
|
|
|
|
|
|
|
|
mockMvc.post("/persons").andExpect {
|
|
|
|
|
status().isOk()
|
|
|
|
|
model {
|
|
|
|
|
attributeHasErrors("person")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Many times, when writing tests, it is useful to dump the results of the performed
|
|
|
|
|
request. You can do so as follows, where `print()` is a static import from
|
2015-03-03 18:38:01 +08:00
|
|
|
|
`MockMvcResultHandlers`:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
mockMvc.perform(post("/persons"))
|
|
|
|
|
.andDo(print())
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(model().attributeHasErrors("person"));
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
import org.springframework.test.web.servlet.post
|
|
|
|
|
|
|
|
|
|
mockMvc.post("/persons").andDo {
|
|
|
|
|
print()
|
|
|
|
|
}.andExpect {
|
|
|
|
|
status().isOk()
|
|
|
|
|
model {
|
|
|
|
|
attributeHasErrors("person")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-07-03 03:47:14 +08:00
|
|
|
|
As long as request processing does not cause an unhandled exception, the `print()` method
|
2019-09-26 21:02:30 +08:00
|
|
|
|
prints all the available result data to `System.out`. There is also a `log()` method and
|
|
|
|
|
two additional variants of the `print()` method, one that accepts an `OutputStream` and
|
|
|
|
|
one that accepts a `Writer`. For example, invoking `print(System.err)` prints the result
|
|
|
|
|
data to `System.err`, while invoking `print(myWriter)` prints the result data to a custom
|
|
|
|
|
writer. If you want to have the result data logged instead of printed, you can invoke the
|
|
|
|
|
`log()` method, which logs the result data as a single `DEBUG` message under the
|
|
|
|
|
`org.springframework.test.web.servlet.result` logging category.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
In some cases, you may want to get direct access to the result and verify something that
|
2015-10-08 22:57:31 +08:00
|
|
|
|
cannot be verified otherwise. This can be achieved by appending `.andReturn()` after all
|
2018-08-30 23:29:17 +08:00
|
|
|
|
other expectations, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
|
|
|
|
|
// ...
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
var mvcResult = mockMvc.post("/persons").andExpect { status().isOk() }.andReturn()
|
|
|
|
|
// ...
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
If all tests repeat the same expectations, you can set up common expectations once when
|
|
|
|
|
building the `MockMvc` instance, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
standaloneSetup(new SimpleController())
|
|
|
|
|
.alwaysExpect(status().isOk())
|
|
|
|
|
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
|
|
|
|
|
.build()
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Note that common expectations are always applied and cannot be overridden without
|
2018-09-18 21:43:14 +08:00
|
|
|
|
creating a separate `MockMvc` instance.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
When a JSON response content contains hypermedia links created with
|
2018-09-18 22:42:09 +08:00
|
|
|
|
https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the
|
|
|
|
|
resulting links by using JsonPath expressions, as the following example shows:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
|
2016-04-06 20:14:51 +08:00
|
|
|
|
.andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
mockMvc.get("/people") {
|
|
|
|
|
accept(MediaType.APPLICATION_JSON)
|
|
|
|
|
}.andExpect {
|
|
|
|
|
jsonPath("$.links[?(@.rel == 'self')].href") {
|
|
|
|
|
value("http://localhost:8080/people")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
When XML response content contains hypermedia links created with
|
2018-09-18 22:42:09 +08:00
|
|
|
|
https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the
|
|
|
|
|
resulting links by using XPath expressions:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
|
|
|
|
|
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
|
2016-04-06 20:14:51 +08:00
|
|
|
|
.andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
|
|
|
|
|
mockMvc.get("/handle") {
|
|
|
|
|
accept(MediaType.APPLICATION_XML)
|
|
|
|
|
}.andExpect {
|
|
|
|
|
xpath("/person/ns:link[@rel='self']/@href", ns) {
|
|
|
|
|
string("http://localhost:8080/people")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2019-07-05 17:30:02 +08:00
|
|
|
|
[[spring-mvc-test-async-requests]]
|
|
|
|
|
===== Async Requests
|
|
|
|
|
|
|
|
|
|
Servlet 3.0 asynchronous requests,
|
|
|
|
|
<<web.adoc#mvc-ann-async,supported in Spring MVC>>, work by exiting the Servlet container
|
|
|
|
|
thread and allowing the application to compute the response asynchronously, after which
|
|
|
|
|
an async dispatch is made to complete processing on a Servlet container thread.
|
|
|
|
|
|
|
|
|
|
In Spring MVC Test, async requests can be tested by asserting the produced async value
|
|
|
|
|
first, then manually performing the async dispatch, and finally verifying the response.
|
|
|
|
|
Below is an example test for controller methods that return `DeferredResult`, `Callable`,
|
|
|
|
|
or reactive type such as Reactor `Mono`:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
@Test
|
|
|
|
|
void test() throws Exception {
|
|
|
|
|
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
|
|
|
|
|
.andExpect(status().isOk()) <1>
|
|
|
|
|
.andExpect(request().asyncStarted()) <2>
|
|
|
|
|
.andExpect(request().asyncResult("body")) <3>
|
|
|
|
|
.andReturn();
|
|
|
|
|
|
|
|
|
|
this.mockMvc.perform(asyncDispatch(mvcResult)) <4>
|
|
|
|
|
.andExpect(status().isOk()) <5>
|
|
|
|
|
.andExpect(content().string("body"));
|
|
|
|
|
}
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
2019-07-05 17:30:02 +08:00
|
|
|
|
<1> Check response status is still unchanged
|
|
|
|
|
<2> Async processing must have started
|
|
|
|
|
<3> Wait and assert the async result
|
|
|
|
|
<4> Manually perform an ASYNC dispatch (as there is no running container)
|
|
|
|
|
<5> Verify the final response
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
fun test() {
|
|
|
|
|
var mvcResult = mockMvc.get("/path").andExpect {
|
|
|
|
|
status().isOk() // <1>
|
|
|
|
|
request { asyncStarted() } // <2>
|
|
|
|
|
// TODO Remove unused generic parameter
|
|
|
|
|
request { asyncResult<Nothing>("body") } // <3>
|
|
|
|
|
}.andReturn()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mockMvc.perform(asyncDispatch(mvcResult)) // <4>
|
|
|
|
|
.andExpect {
|
|
|
|
|
status().isOk() // <5>
|
|
|
|
|
content().string("body")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> Check response status is still unchanged
|
|
|
|
|
<2> Async processing must have started
|
|
|
|
|
<3> Wait and assert the async result
|
|
|
|
|
<4> Manually perform an ASYNC dispatch (as there is no running container)
|
|
|
|
|
<5> Verify the final response
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-03-11 21:13:49 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-vs-streaming-response]]
|
|
|
|
|
===== Streaming Responses
|
|
|
|
|
|
|
|
|
|
There are no options built into Spring MVC Test for container-less testing of streaming
|
|
|
|
|
responses. Applications that make use of
|
|
|
|
|
<<web.adoc#mvc-ann-async-http-streaming,Spring MVC streaming>> options can use the
|
|
|
|
|
<<testing.adoc#webtestclient-stream,WebTestClient>> to perform end-to-end, integration
|
|
|
|
|
tests against a running server. This is also supported in Spring Boot where you can
|
2019-10-23 03:00:06 +08:00
|
|
|
|
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server]
|
2019-03-11 21:13:49 +08:00
|
|
|
|
with `WebTestClient`. One extra advantage is the ability to use the `StepVerifier` from
|
|
|
|
|
project Reactor that allows declaring expectations on a stream of data.
|
|
|
|
|
|
|
|
|
|
|
2019-07-05 17:30:02 +08:00
|
|
|
|
[[spring-mvc-test-server-filters]]
|
|
|
|
|
===== Filter Registrations
|
|
|
|
|
|
|
|
|
|
When setting up a `MockMvc` instance, you can register one or more Servlet `Filter`
|
|
|
|
|
instances, as the following example shows:
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2019-07-05 17:30:02 +08:00
|
|
|
|
----
|
|
|
|
|
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
|
|
|
|
|
----
|
2019-07-05 17:30:02 +08:00
|
|
|
|
|
|
|
|
|
Registered filters are invoked through the `MockFilterChain` from `spring-test`, and the
|
|
|
|
|
last filter delegates to the `DispatcherServlet`.
|
|
|
|
|
|
|
|
|
|
|
2015-07-07 11:06:40 +08:00
|
|
|
|
[[spring-mvc-test-vs-end-to-end-integration-tests]]
|
2019-07-05 17:30:02 +08:00
|
|
|
|
===== Spring MVC Test vs End-to-End Tests
|
2015-07-07 11:06:40 +08:00
|
|
|
|
|
2019-07-05 17:30:02 +08:00
|
|
|
|
Spring MVC Test is built on Servlet API mock implementations from the
|
|
|
|
|
`spring-test` module and does not rely on a running container. Therefore, there are
|
|
|
|
|
some differences when compared to full end-to-end integration tests with an actual
|
|
|
|
|
client and a live server running.
|
2015-07-07 11:06:40 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
The easiest way to think about this is by starting with a blank `MockHttpServletRequest`.
|
|
|
|
|
Whatever you add to it is what the request becomes. Things that may catch you by surprise
|
2018-09-18 22:42:09 +08:00
|
|
|
|
are that there is no context path by default; no `jsessionid` cookie; no forwarding,
|
|
|
|
|
error, or async dispatches; and, therefore, no actual JSP rendering. Instead,
|
|
|
|
|
"`forwarded`" and "`redirected`" URLs are saved in the `MockHttpServletResponse` and can
|
|
|
|
|
be asserted with expectations.
|
2015-07-07 11:06:40 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
This means that, if you use JSPs, you can verify the JSP page to which the request was
|
2018-09-18 22:42:09 +08:00
|
|
|
|
forwarded, but no HTML is rendered. In other words, the JSP is not invoked. Note,
|
|
|
|
|
however, that all other rendering technologies that do not rely on forwarding, such as
|
|
|
|
|
Thymeleaf and Freemarker, render HTML to the response body as expected. The same is true
|
|
|
|
|
for rendering JSON, XML, and other formats through `@ResponseBody` methods.
|
2015-07-07 11:06:40 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Alternatively, you may consider the full end-to-end integration testing support from
|
2019-10-23 03:00:06 +08:00
|
|
|
|
Spring Boot with `@SpringBootTest`. See the
|
|
|
|
|
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing[Spring Boot Reference Guide].
|
2015-07-07 11:06:40 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
There are pros and cons for each approach. The options provided in Spring MVC Test are
|
|
|
|
|
different stops on the scale from classic unit testing to full integration testing. To be
|
|
|
|
|
certain, none of the options in Spring MVC Test fall under the category of classic unit
|
|
|
|
|
testing, but they are a little closer to it. For example, you can isolate the web layer
|
|
|
|
|
by injecting mocked services into controllers, in which case you are testing the web
|
|
|
|
|
layer only through the `DispatcherServlet` but with actual Spring configuration, as you
|
|
|
|
|
might test the data access layer in isolation from the layers above it. Also, you can use
|
|
|
|
|
the stand-alone setup, focusing on one controller at a time and manually providing the
|
|
|
|
|
configuration required to make it work.
|
2015-07-07 11:06:40 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Another important distinction when using Spring MVC Test is that, conceptually, such
|
2018-09-18 22:42:09 +08:00
|
|
|
|
tests are the server-side, so you can check what handler was used, if an exception was
|
|
|
|
|
handled with a HandlerExceptionResolver, what the content of the model is, what binding
|
|
|
|
|
errors there were, and other details. That means that it is easier to write expectations,
|
2020-06-17 20:22:06 +08:00
|
|
|
|
since the server is not an opaque box, as it is when testing it through an actual HTTP
|
2018-09-18 22:42:09 +08:00
|
|
|
|
client. This is generally an advantage of classic unit testing: It is easier to write,
|
|
|
|
|
reason about, and debug but does not replace the need for full integration tests. At the
|
|
|
|
|
same time, it is important not to lose sight of the fact that the response is the most
|
|
|
|
|
important thing to check. In short, there is room here for multiple styles and strategies
|
|
|
|
|
of testing even within the same project.
|
2015-07-07 11:06:40 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[spring-mvc-test-server-resources]]
|
2019-07-05 17:30:02 +08:00
|
|
|
|
===== Further Examples
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
The framework's own tests include
|
|
|
|
|
https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples[many
|
2018-08-30 23:29:17 +08:00
|
|
|
|
sample tests] intended to show how to use Spring MVC Test. You can browse these examples
|
|
|
|
|
for further ideas. Also, the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
https://github.com/spring-projects/spring-mvc-showcase[`spring-mvc-showcase`] project has
|
|
|
|
|
full test coverage based on Spring MVC Test.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
[[spring-mvc-test-server-htmlunit]]
|
|
|
|
|
==== HtmlUnit Integration
|
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
Spring provides integration between <<spring-mvc-test-server, MockMvc>> and
|
2015-07-29 00:46:18 +08:00
|
|
|
|
http://htmlunit.sourceforge.net/[HtmlUnit]. This simplifies performing end-to-end testing
|
2018-08-30 23:29:17 +08:00
|
|
|
|
when using HTML-based views. This integration lets you:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Easily test HTML pages by using tools such as
|
|
|
|
|
http://htmlunit.sourceforge.net/[HtmlUnit],
|
2019-03-21 06:48:14 +08:00
|
|
|
|
https://www.seleniumhq.org[WebDriver], and
|
2018-09-21 20:45:25 +08:00
|
|
|
|
http://www.gebish.org/manual/current/#spock-junit-testng[Geb] without the need to
|
|
|
|
|
deploy to a Servlet container.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* Test JavaScript within pages.
|
|
|
|
|
* Optionally, test using mock services to speed up testing.
|
|
|
|
|
* Share logic between in-container end-to-end tests and out-of-container integration tests.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
NOTE: MockMvc works with templating technologies that do not rely on a Servlet Container
|
|
|
|
|
(for example, Thymeleaf, FreeMarker, and others), but it does not work with JSPs, since
|
|
|
|
|
they rely on the Servlet container.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
[[spring-mvc-test-server-htmlunit-why]]
|
|
|
|
|
===== Why HtmlUnit Integration?
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The most obvious question that comes to mind is "`Why do I need this?`" The answer is
|
|
|
|
|
best found by exploring a very basic sample application. Assume you have a Spring MVC web
|
|
|
|
|
application that supports CRUD operations on a `Message` object. The application also
|
|
|
|
|
supports paging through all messages. How would you go about testing it?
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
With Spring MVC Test, we can easily test if we are able to create a `Message`, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
MockHttpServletRequestBuilder createMessage = post("/messages/")
|
|
|
|
|
.param("summary", "Spring Rocks")
|
|
|
|
|
.param("text", "In case you didn't know, Spring Rocks!");
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
mockMvc.perform(createMessage)
|
|
|
|
|
.andExpect(status().is3xxRedirection())
|
|
|
|
|
.andExpect(redirectedUrl("/messages/123"));
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@Test
|
|
|
|
|
fun test() {
|
|
|
|
|
mockMvc.post("/messages/") {
|
|
|
|
|
param("summary", "Spring Rocks")
|
|
|
|
|
param("text", "In case you didn't know, Spring Rocks!")
|
|
|
|
|
}.andExpect {
|
|
|
|
|
status().is3xxRedirection()
|
|
|
|
|
redirectedUrl("/messages/123")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
What if we want to test the form view that lets us create the message? For example,
|
2015-06-24 00:31:48 +08:00
|
|
|
|
assume our form looks like the following snippet:
|
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
[source,xml,indent=0]
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
<form id="messageForm" action="/messages/" method="post">
|
|
|
|
|
<div class="pull-right"><a href="/messages/">Messages</a></div>
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
<label for="summary">Summary</label>
|
|
|
|
|
<input type="text" class="required" id="summary" name="summary" value="" />
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
<label for="text">Message</label>
|
|
|
|
|
<textarea id="text" name="text"></textarea>
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
<div class="form-actions">
|
|
|
|
|
<input type="submit" value="Create" />
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
How do we ensure that our form produce the correct request to create a new message? A
|
|
|
|
|
naive attempt might resemble the following:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
mockMvc.perform(get("/messages/form"))
|
|
|
|
|
.andExpect(xpath("//input[@name='summary']").exists())
|
|
|
|
|
.andExpect(xpath("//textarea[@name='text']").exists());
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
mockMvc.get("/messages/form").andExpect {
|
|
|
|
|
xpath("//input[@name='summary']") { exists() }
|
|
|
|
|
xpath("//textarea[@name='text']") { exists() }
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
This test has some obvious drawbacks. If we update our controller to use the parameter
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`message` instead of `text`, our form test continues to pass, even though the HTML form
|
|
|
|
|
is out of synch with the controller. To resolve this we can combine our two tests, as
|
|
|
|
|
follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-mock-mvc-test]]
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
String summaryParamName = "summary";
|
|
|
|
|
String textParamName = "text";
|
|
|
|
|
mockMvc.perform(get("/messages/form"))
|
|
|
|
|
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
|
|
|
|
|
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
MockHttpServletRequestBuilder createMessage = post("/messages/")
|
|
|
|
|
.param(summaryParamName, "Spring Rocks")
|
|
|
|
|
.param(textParamName, "In case you didn't know, Spring Rocks!");
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
mockMvc.perform(createMessage)
|
|
|
|
|
.andExpect(status().is3xxRedirection())
|
|
|
|
|
.andExpect(redirectedUrl("/messages/123"));
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
val summaryParamName = "summary";
|
|
|
|
|
val textParamName = "text";
|
|
|
|
|
mockMvc.get("/messages/form").andExpect {
|
|
|
|
|
xpath("//input[@name='$summaryParamName']") { exists() }
|
|
|
|
|
xpath("//textarea[@name='$textParamName']") { exists() }
|
|
|
|
|
}
|
|
|
|
|
mockMvc.post("/messages/") {
|
|
|
|
|
param(summaryParamName, "Spring Rocks")
|
|
|
|
|
param(textParamName, "In case you didn't know, Spring Rocks!")
|
|
|
|
|
}.andExpect {
|
|
|
|
|
status().is3xxRedirection()
|
|
|
|
|
redirectedUrl("/messages/123")
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
This would reduce the risk of our test incorrectly passing, but there are still some
|
2018-08-30 23:29:17 +08:00
|
|
|
|
problems:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* What if we have multiple forms on our page? Admittedly, we could update our XPath
|
2018-09-18 22:42:09 +08:00
|
|
|
|
expressions, but they get more complicated as we take more factors into account: Are
|
|
|
|
|
the fields the correct type? Are the fields enabled? And so on.
|
|
|
|
|
* Another issue is that we are doing double the work we would expect. We must first
|
|
|
|
|
verify the view, and then we submit the view with the same parameters we just verified.
|
|
|
|
|
Ideally, this could be done all at once.
|
|
|
|
|
* Finally, we still cannot account for some things. For example, what if the form has
|
|
|
|
|
JavaScript validation that we wish to test as well?
|
2015-07-29 00:46:18 +08:00
|
|
|
|
|
|
|
|
|
The overall problem is that testing a web page does not involve a single interaction.
|
|
|
|
|
Instead, it is a combination of how the user interacts with a web page and how that web
|
|
|
|
|
page interacts with other resources. For example, the result of a form view is used as
|
2018-08-30 23:29:17 +08:00
|
|
|
|
the input to a user for creating a message. In addition, our form view can potentially
|
|
|
|
|
use additional resources that impact the behavior of the page, such as JavaScript
|
2015-07-29 00:46:18 +08:00
|
|
|
|
validation.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-why-integration]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Integration Testing to the Rescue?
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
To resolve the issues mentioned earlier, we could perform end-to-end integration testing,
|
|
|
|
|
but this has some drawbacks. Consider testing the view that lets us page through the
|
|
|
|
|
messages. We might need the following tests:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Does our page display a notification to the user to indicate that no results are
|
|
|
|
|
available when the messages are empty?
|
2015-06-24 00:31:48 +08:00
|
|
|
|
* Does our page properly display a single message?
|
|
|
|
|
* Does our page properly support paging?
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
To set up these tests, we need to ensure our database contains the proper messages. This
|
|
|
|
|
leads to a number of additional challenges:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
* Ensuring the proper messages are in the database can be tedious. (Consider foreign key
|
|
|
|
|
constraints.)
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Testing can become slow, since each test would need to ensure that the database is in
|
|
|
|
|
the correct state.
|
2015-07-29 00:46:18 +08:00
|
|
|
|
* Since our database needs to be in a specific state, we cannot run tests in parallel.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
* Performing assertions on such items as auto-generated ids, timestamps, and others can
|
|
|
|
|
be difficult.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
These challenges do not mean that we should abandon end-to-end integration testing
|
|
|
|
|
altogether. Instead, we can reduce the number of end-to-end integration tests by
|
2018-09-18 22:42:09 +08:00
|
|
|
|
refactoring our detailed tests to use mock services that run much faster, more reliably,
|
|
|
|
|
and without side effects. We can then implement a small number of true end-to-end
|
|
|
|
|
integration tests that validate simple workflows to ensure that everything works together
|
|
|
|
|
properly.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-why-mockmvc]]
|
|
|
|
|
====== Enter HtmlUnit Integration
|
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
So how can we achieve a balance between testing the interactions of our pages and still
|
2018-08-30 23:29:17 +08:00
|
|
|
|
retain good performance within our test suite? The answer is: "`By integrating MockMvc
|
|
|
|
|
with HtmlUnit.`"
|
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
[[spring-mvc-test-server-htmlunit-options]]
|
|
|
|
|
====== HtmlUnit Integration Options
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
You have a number of options when you want to integrate MockMvc with HtmlUnit:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
* <<spring-mvc-test-server-htmlunit-mah,MockMvc and HtmlUnit>>: Use this option if you
|
2018-09-18 22:42:09 +08:00
|
|
|
|
want to use the raw HtmlUnit libraries.
|
2015-07-29 00:46:18 +08:00
|
|
|
|
* <<spring-mvc-test-server-htmlunit-webdriver,MockMvc and WebDriver>>: Use this option to
|
2018-09-18 22:42:09 +08:00
|
|
|
|
ease development and reuse code between integration and end-to-end testing.
|
|
|
|
|
* <<spring-mvc-test-server-htmlunit-geb,MockMvc and Geb>>: Use this option if you want to
|
|
|
|
|
use Groovy for testing, ease development, and reuse code between integration and
|
|
|
|
|
end-to-end testing.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-mah]]
|
|
|
|
|
===== MockMvc and HtmlUnit
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
This section describes how to integrate MockMvc and HtmlUnit. Use this option if you want
|
|
|
|
|
to use the raw HtmlUnit libraries.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-mah-setup]]
|
|
|
|
|
====== MockMvc and HtmlUnit Setup
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
First, make sure that you have included a test dependency on
|
|
|
|
|
`net.sourceforge.htmlunit:htmlunit`. In order to use HtmlUnit with Apache HttpComponents
|
|
|
|
|
4.5+, you need to use HtmlUnit 2.18 or higher.
|
2015-09-01 06:53:00 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
|
|
|
|
|
`MockMvcWebClientBuilder`, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
WebClient webClient;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup(WebApplicationContext context) {
|
2017-11-21 05:28:00 +08:00
|
|
|
|
webClient = MockMvcWebClientBuilder
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.build();
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
lateinit var webClient: WebClient
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup(context: WebApplicationContext) {
|
|
|
|
|
webClient = MockMvcWebClientBuilder
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
|
|
|
|
|
see <<spring-mvc-test-server-htmlunit-mah-advanced-builder>>.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
This ensures that any URL that references `localhost` as the server is directed to our
|
|
|
|
|
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
|
2018-08-30 23:29:17 +08:00
|
|
|
|
requested by using a network connection, as normal. This lets us easily test the use of
|
2015-07-29 00:46:18 +08:00
|
|
|
|
CDNs.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-mah-usage]]
|
|
|
|
|
====== MockMvc and HtmlUnit Usage
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Now we can use HtmlUnit as we normally would but without the need to deploy our
|
2018-09-18 22:42:09 +08:00
|
|
|
|
application to a Servlet container. For example, we can request the view to create a
|
|
|
|
|
message with the following:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
|
|
|
|
|
as described in <<spring-mvc-test-server-htmlunit-mah-advanced-builder>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it
|
|
|
|
|
to create a message, as the following example shows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
|
|
|
|
|
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
|
|
|
|
|
summaryInput.setValueAttribute("Spring Rocks");
|
|
|
|
|
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
|
|
|
|
|
textInput.setText("In case you didn't know, Spring Rocks!");
|
|
|
|
|
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
|
|
|
|
|
HtmlPage newMessagePage = submit.click();
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
val form = createMsgFormPage.getHtmlElementById("messageForm")
|
|
|
|
|
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
|
|
|
|
|
summaryInput.setValueAttribute("Spring Rocks")
|
|
|
|
|
val textInput = createMsgFormPage.getHtmlElementById("text")
|
|
|
|
|
textInput.setText("In case you didn't know, Spring Rocks!")
|
|
|
|
|
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
|
|
|
|
|
val newMessagePage = submit.click()
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
Finally, we can verify that a new message was created successfully. The following
|
2019-03-21 06:48:14 +08:00
|
|
|
|
assertions use the https://joel-costigliola.github.io/assertj/[AssertJ] library:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
|
|
|
|
|
String id = newMessagePage.getHtmlElementById("id").getTextContent();
|
|
|
|
|
assertThat(id).isEqualTo("123");
|
|
|
|
|
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
|
|
|
|
|
assertThat(summary).isEqualTo("Spring Rocks");
|
|
|
|
|
String text = newMessagePage.getHtmlElementById("text").getTextContent();
|
|
|
|
|
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
|
|
|
|
|
val id = newMessagePage.getHtmlElementById("id").getTextContent()
|
|
|
|
|
assertThat(id).isEqualTo("123")
|
|
|
|
|
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
|
|
|
|
|
assertThat(summary).isEqualTo("Spring Rocks")
|
|
|
|
|
val text = newMessagePage.getHtmlElementById("text").getTextContent()
|
|
|
|
|
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The preceding code improves on our
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<spring-mvc-test-server-htmlunit-mock-mvc-test, MockMvc test>> in a number of ways.
|
2018-09-18 22:42:09 +08:00
|
|
|
|
First, we no longer have to explicitly verify our form and then create a request that
|
|
|
|
|
looks like the form. Instead, we request the form, fill it out, and submit it, thereby
|
|
|
|
|
significantly reducing the overhead.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
Another important factor is that http://htmlunit.sourceforge.net/javascript.html[HtmlUnit
|
2018-09-18 22:42:09 +08:00
|
|
|
|
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test
|
|
|
|
|
the behavior of JavaScript within our pages.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
See the http://htmlunit.sourceforge.net/gettingStarted.html[HtmlUnit documentation] for
|
|
|
|
|
additional information about using HtmlUnit.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-mah-advanced-builder]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Advanced `MockMvcWebClientBuilder`
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In the examples so far, we have used `MockMvcWebClientBuilder` in the simplest way
|
|
|
|
|
possible, by building a `WebClient` based on the `WebApplicationContext` loaded for us by
|
|
|
|
|
the Spring TestContext Framework. This approach is repeated in the following example:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
WebClient webClient;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup(WebApplicationContext context) {
|
2017-11-21 05:28:00 +08:00
|
|
|
|
webClient = MockMvcWebClientBuilder
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.build();
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
lateinit var webClient: WebClient
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup(context: WebApplicationContext) {
|
|
|
|
|
webClient = MockMvcWebClientBuilder
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We can also specify additional configuration options, as the following example shows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
WebClient webClient;
|
2015-07-29 00:46:18 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup() {
|
2017-11-21 05:28:00 +08:00
|
|
|
|
webClient = MockMvcWebClientBuilder
|
|
|
|
|
// demonstrates applying a MockMvcConfigurer (Spring Security)
|
|
|
|
|
.webAppContextSetup(context, springSecurity())
|
|
|
|
|
// for illustration only - defaults to ""
|
|
|
|
|
.contextPath("")
|
|
|
|
|
// By default MockMvc is used for localhost only;
|
|
|
|
|
// the following will use MockMvc for example.com and example.org as well
|
|
|
|
|
.useMockMvcForHosts("example.com","example.org")
|
|
|
|
|
.build();
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
lateinit var webClient: WebClient
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup() {
|
|
|
|
|
webClient = MockMvcWebClientBuilder
|
|
|
|
|
// demonstrates applying a MockMvcConfigurer (Spring Security)
|
|
|
|
|
.webAppContextSetup(context, springSecurity())
|
|
|
|
|
// for illustration only - defaults to ""
|
|
|
|
|
.contextPath("")
|
|
|
|
|
// By default MockMvc is used for localhost only;
|
|
|
|
|
// the following will use MockMvc for example.com and example.org as well
|
|
|
|
|
.useMockMvcForHosts("example.com","example.org")
|
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
As an alternative, we can perform the exact same setup by configuring the `MockMvc`
|
2018-08-30 23:29:17 +08:00
|
|
|
|
instance separately and supplying it to the `MockMvcWebClientBuilder`, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
MockMvc mockMvc = MockMvcBuilders
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.apply(springSecurity())
|
|
|
|
|
.build();
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
webClient = MockMvcWebClientBuilder
|
|
|
|
|
.mockMvcSetup(mockMvc)
|
|
|
|
|
// for illustration only - defaults to ""
|
|
|
|
|
.contextPath("")
|
|
|
|
|
// By default MockMvc is used for localhost only;
|
|
|
|
|
// the following will use MockMvc for example.com and example.org as well
|
|
|
|
|
.useMockMvcForHosts("example.com","example.org")
|
|
|
|
|
.build();
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
This is more verbose, but, by building the `WebClient` with a `MockMvc` instance, we have
|
2018-08-30 23:29:17 +08:00
|
|
|
|
the full power of MockMvc at our fingertips.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
TIP: For additional information on creating a `MockMvc` instance, see
|
2015-06-24 00:31:48 +08:00
|
|
|
|
<<spring-mvc-test-server-setup-options>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
[[spring-mvc-test-server-htmlunit-webdriver]]
|
|
|
|
|
===== MockMvc and WebDriver
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In the previous sections, we have seen how to use MockMvc in conjunction with the raw
|
|
|
|
|
HtmlUnit APIs. In this section, we use additional abstractions within the Selenium
|
2019-03-21 06:48:14 +08:00
|
|
|
|
https://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-webdriver-why]]
|
|
|
|
|
====== Why WebDriver and MockMvc?
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver? The
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Selenium WebDriver provides a very elegant API that lets us easily organize our code. To
|
|
|
|
|
better show how it works, we explore an example in this section.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-03-21 06:48:14 +08:00
|
|
|
|
NOTE: Despite being a part of https://docs.seleniumhq.org/[Selenium], WebDriver does not
|
2018-09-18 22:42:09 +08:00
|
|
|
|
require a Selenium Server to run your tests.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
Suppose we need to ensure that a message is created properly. The tests involve finding
|
2015-07-29 00:46:18 +08:00
|
|
|
|
the HTML form input elements, filling them out, and making various assertions.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
This approach results in numerous separate tests because we want to test error conditions
|
|
|
|
|
as well. For example, we want to ensure that we get an error if we fill out only part of
|
|
|
|
|
the form. If we fill out the entire form, the newly created message should be displayed
|
|
|
|
|
afterwards.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
If one of the fields were named "`summary`", we might have something that resembles the
|
|
|
|
|
following repeated in multiple places within our tests:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
|
|
|
|
|
summaryInput.setValueAttribute(summary);
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
val summaryInput = currentPage.getHtmlElementById("summary")
|
|
|
|
|
summaryInput.setValueAttribute(summary)
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
So what happens if we change the `id` to `smmry`? Doing so would force us to update all
|
2018-09-18 22:42:09 +08:00
|
|
|
|
of our tests to incorporate this change. This violates the DRY principle, so we should
|
|
|
|
|
ideally extract this code into its own method, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
|
|
|
|
|
setSummary(currentPage, summary);
|
|
|
|
|
// ...
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public void setSummary(HtmlPage currentPage, String summary) {
|
|
|
|
|
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
|
|
|
|
|
summaryInput.setValueAttribute(summary);
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
|
|
|
|
|
setSummary(currentPage, summary);
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun setSummary(currentPage:HtmlPage , summary: String) {
|
|
|
|
|
val summaryInput = currentPage.getHtmlElementById("summary")
|
|
|
|
|
summaryInput.setValueAttribute(summary)
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Doing so ensures that we do not have to update all of our tests if we change the UI.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We might even take this a step further and place this logic within an `Object` that
|
|
|
|
|
represents the `HtmlPage` we are currently on, as the following example shows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public class CreateMessagePage {
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
final HtmlPage currentPage;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
final HtmlTextInput summaryInput;
|
2015-07-29 00:46:18 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
final HtmlSubmitInput submit;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public CreateMessagePage(HtmlPage currentPage) {
|
|
|
|
|
this.currentPage = currentPage;
|
|
|
|
|
this.summaryInput = currentPage.getHtmlElementById("summary");
|
|
|
|
|
this.submit = currentPage.getHtmlElementById("submit");
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public <T> T createMessage(String summary, String text) throws Exception {
|
|
|
|
|
setSummary(summary);
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
HtmlPage result = submit.click();
|
|
|
|
|
boolean error = CreateMessagePage.at(result);
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public void setSummary(String summary) throws Exception {
|
|
|
|
|
summaryInput.setValueAttribute(summary);
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public static boolean at(HtmlPage page) {
|
|
|
|
|
return "Create Message".equals(page.getTitleText());
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
class CreateMessagePage(private val currentPage: HtmlPage) {
|
|
|
|
|
|
|
|
|
|
val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")
|
|
|
|
|
|
|
|
|
|
val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")
|
|
|
|
|
|
|
|
|
|
fun <T> createMessage(summary: String, text: String): T {
|
|
|
|
|
setSummary(summary)
|
|
|
|
|
|
|
|
|
|
val result = submit.click()
|
|
|
|
|
val error = at(result)
|
|
|
|
|
|
|
|
|
|
return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun setSummary(summary: String) {
|
|
|
|
|
summaryInput.setValueAttribute(summary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun at(page: HtmlPage): Boolean {
|
|
|
|
|
return "Create Message" == page.getTitleText()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Formerly, this pattern was known as the
|
2018-09-18 22:42:09 +08:00
|
|
|
|
https://github.com/SeleniumHQ/selenium/wiki/PageObjects[Page Object Pattern]. While we
|
|
|
|
|
can certainly do this with HtmlUnit, WebDriver provides some tools that we explore in the
|
2015-07-29 00:46:18 +08:00
|
|
|
|
following sections to make this pattern much easier to implement.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-webdriver-setup]]
|
|
|
|
|
====== MockMvc and WebDriver Setup
|
|
|
|
|
|
2015-09-01 06:53:00 +08:00
|
|
|
|
To use Selenium WebDriver with the Spring MVC Test framework, make sure that your project
|
|
|
|
|
includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver`.
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We can easily create a Selenium WebDriver that integrates with MockMvc by using the
|
|
|
|
|
`MockMvcHtmlUnitDriverBuilder` as the following example shows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
WebDriver driver;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup(WebApplicationContext context) {
|
2017-11-21 05:28:00 +08:00
|
|
|
|
driver = MockMvcHtmlUnitDriverBuilder
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.build();
|
2019-08-30 08:35:53 +08:00
|
|
|
|
}
|
|
|
|
|
----
|
2019-08-31 20:47:49 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
2019-08-30 08:35:53 +08:00
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
lateinit var driver: WebDriver
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup(context: WebApplicationContext) {
|
|
|
|
|
driver = MockMvcHtmlUnitDriverBuilder
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.build()
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
|
|
|
|
usage, see <<spring-mvc-test-server-htmlunit-webdriver-advanced-builder>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
The preceding example ensures that any URL that references `localhost` as the server is
|
|
|
|
|
directed to our `MockMvc` instance without the need for a real HTTP connection. Any other
|
|
|
|
|
URL is requested by using a network connection, as normal. This lets us easily test the
|
|
|
|
|
use of CDNs.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-webdriver-usage]]
|
|
|
|
|
====== MockMvc and WebDriver Usage
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Now we can use WebDriver as we normally would but without the need to deploy our
|
2018-09-18 22:42:09 +08:00
|
|
|
|
application to a Servlet container. For example, we can request the view to create a
|
|
|
|
|
message with the following:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
CreateMessagePage page = CreateMessagePage.to(driver);
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
val page = CreateMessagePage.to(driver)
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We can then fill out the form and submit it to create a message, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
ViewMessagePage viewMessagePage =
|
|
|
|
|
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
val viewMessagePage =
|
|
|
|
|
page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)
|
|
|
|
|
----
|
|
|
|
|
|
2019-03-05 20:08:34 +08:00
|
|
|
|
This improves on the design of our <<spring-mvc-test-server-htmlunit-mah-usage, HtmlUnit test>>
|
|
|
|
|
by leveraging the Page Object Pattern. As we mentioned in
|
2018-09-18 22:42:09 +08:00
|
|
|
|
<<spring-mvc-test-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern
|
|
|
|
|
with HtmlUnit, but it is much easier with WebDriver. Consider the following
|
|
|
|
|
`CreateMessagePage` implementation:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public class CreateMessagePage
|
|
|
|
|
extends AbstractPage { // <1>
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// <2>
|
|
|
|
|
private WebElement summary;
|
|
|
|
|
private WebElement text;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
// <3>
|
|
|
|
|
@FindBy(css = "input[type=submit]")
|
|
|
|
|
private WebElement submit;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public CreateMessagePage(WebDriver driver) {
|
|
|
|
|
super(driver);
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
|
|
|
|
|
this.summary.sendKeys(summary);
|
|
|
|
|
this.text.sendKeys(details);
|
|
|
|
|
this.submit.click();
|
|
|
|
|
return PageFactory.initElements(driver, resultPage);
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public static CreateMessagePage to(WebDriver driver) {
|
|
|
|
|
driver.get("http://localhost:9990/mail/messages/form");
|
|
|
|
|
return PageFactory.initElements(driver, CreateMessagePage.class);
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
}
|
|
|
|
|
----
|
2018-09-18 22:42:09 +08:00
|
|
|
|
<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of
|
|
|
|
|
`AbstractPage`, but, in summary, it contains common functionality for all of our pages.
|
|
|
|
|
For example, if our application has a navigational bar, global error messages, and other
|
|
|
|
|
features, we can place this logic in a shared location.
|
|
|
|
|
<2> We have a member variable for each of the parts of the HTML page in which we are
|
|
|
|
|
interested. These are of type `WebElement`. WebDriver's
|
|
|
|
|
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
|
|
|
|
|
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
|
|
|
|
|
each `WebElement`. The
|
2018-08-30 23:29:17 +08:00
|
|
|
|
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
|
2018-09-18 22:42:09 +08:00
|
|
|
|
method automatically resolves each `WebElement` by using the field name and looking it up
|
|
|
|
|
by the `id` or `name` of the element within the HTML page.
|
2019-08-30 08:35:53 +08:00
|
|
|
|
<3> We can use the
|
|
|
|
|
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
|
|
|
|
|
to override the default lookup behavior. Our example shows how to use the `@FindBy`
|
|
|
|
|
annotation to look up our submit button with a `css` selector (*input[type=submit]*).
|
|
|
|
|
|
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { // <1>
|
|
|
|
|
|
|
|
|
|
// <2>
|
|
|
|
|
private lateinit var summary: WebElement
|
|
|
|
|
private lateinit var text: WebElement
|
|
|
|
|
|
|
|
|
|
// <3>
|
|
|
|
|
@FindBy(css = "input[type=submit]")
|
|
|
|
|
private lateinit var submit: WebElement
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
|
|
|
|
|
this.summary.sendKeys(summary)
|
|
|
|
|
text.sendKeys(details)
|
|
|
|
|
submit.click()
|
|
|
|
|
return PageFactory.initElements(driver, resultPage)
|
|
|
|
|
}
|
|
|
|
|
companion object {
|
|
|
|
|
fun to(driver: WebDriver): CreateMessagePage {
|
|
|
|
|
driver.get("http://localhost:9990/mail/messages/form")
|
|
|
|
|
return PageFactory.initElements(driver, CreateMessagePage::class.java)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of
|
|
|
|
|
`AbstractPage`, but, in summary, it contains common functionality for all of our pages.
|
|
|
|
|
For example, if our application has a navigational bar, global error messages, and other
|
|
|
|
|
features, we can place this logic in a shared location.
|
|
|
|
|
<2> We have a member variable for each of the parts of the HTML page in which we are
|
|
|
|
|
interested. These are of type `WebElement`. WebDriver's
|
|
|
|
|
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
|
|
|
|
|
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
|
|
|
|
|
each `WebElement`. The
|
|
|
|
|
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
|
|
|
|
|
method automatically resolves each `WebElement` by using the field name and looking it up
|
|
|
|
|
by the `id` or `name` of the element within the HTML page.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
<3> We can use the
|
2018-08-30 23:29:17 +08:00
|
|
|
|
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
|
|
|
|
|
to override the default lookup behavior. Our example shows how to use the `@FindBy`
|
2018-09-18 21:43:14 +08:00
|
|
|
|
annotation to look up our submit button with a `css` selector (*input[type=submit]*).
|
2018-11-27 06:15:55 +08:00
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
Finally, we can verify that a new message was created successfully. The following
|
2019-03-21 06:48:14 +08:00
|
|
|
|
assertions use the https://joel-costigliola.github.io/assertj/[AssertJ] assertion library:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
|
|
|
|
|
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
assertThat(viewMessagePage.message.isEqualTo(expectedMessage)
|
|
|
|
|
assertThat(viewMessagePage.success.isEqualTo("Successfully created a new message")
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
We can see that our `ViewMessagePage` lets us interact with our custom domain model. For
|
|
|
|
|
example, it exposes a method that returns a `Message` object:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
public Message getMessage() throws ParseException {
|
|
|
|
|
Message message = new Message();
|
|
|
|
|
message.setId(getId());
|
|
|
|
|
message.setCreated(getCreated());
|
|
|
|
|
message.setSummary(getSummary());
|
|
|
|
|
message.setText(getText());
|
|
|
|
|
return message;
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We can then use the rich domain objects in our assertions.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Lastly, we must not forget to close the `WebDriver` instance when the test is complete,
|
|
|
|
|
as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@AfterEach
|
|
|
|
|
void destroy() {
|
2017-11-21 05:28:00 +08:00
|
|
|
|
if (driver != null) {
|
|
|
|
|
driver.close();
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
@AfterEach
|
|
|
|
|
fun destroy() {
|
|
|
|
|
if (driver != null) {
|
|
|
|
|
driver.close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
For additional information on using WebDriver, see the Selenium
|
2017-04-19 01:36:46 +08:00
|
|
|
|
https://github.com/SeleniumHQ/selenium/wiki/Getting-Started[WebDriver documentation].
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-webdriver-advanced-builder]]
|
2018-08-30 23:29:17 +08:00
|
|
|
|
====== Advanced `MockMvcHtmlUnitDriverBuilder`
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
In the examples so far, we have used `MockMvcHtmlUnitDriverBuilder` in the simplest way
|
2018-09-18 21:43:14 +08:00
|
|
|
|
possible, by building a `WebDriver` based on the `WebApplicationContext` loaded for us by
|
2018-08-30 23:29:17 +08:00
|
|
|
|
the Spring TestContext Framework. This approach is repeated here, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
WebDriver driver;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup(WebApplicationContext context) {
|
2017-11-21 05:28:00 +08:00
|
|
|
|
driver = MockMvcHtmlUnitDriverBuilder
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.build();
|
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
lateinit var driver: WebDriver
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup(context: WebApplicationContext) {
|
|
|
|
|
driver = MockMvcHtmlUnitDriverBuilder
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We can also specify additional configuration options, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
WebDriver driver;
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-21 21:54:28 +08:00
|
|
|
|
@BeforeEach
|
|
|
|
|
void setup() {
|
2017-11-21 05:28:00 +08:00
|
|
|
|
driver = MockMvcHtmlUnitDriverBuilder
|
|
|
|
|
// demonstrates applying a MockMvcConfigurer (Spring Security)
|
|
|
|
|
.webAppContextSetup(context, springSecurity())
|
|
|
|
|
// for illustration only - defaults to ""
|
|
|
|
|
.contextPath("")
|
|
|
|
|
// By default MockMvc is used for localhost only;
|
|
|
|
|
// the following will use MockMvc for example.com and example.org as well
|
|
|
|
|
.useMockMvcForHosts("example.com","example.org")
|
|
|
|
|
.build();
|
2019-08-31 20:47:49 +08:00
|
|
|
|
}
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
lateinit var driver: WebDriver
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
fun setup() {
|
|
|
|
|
driver = MockMvcHtmlUnitDriverBuilder
|
|
|
|
|
// demonstrates applying a MockMvcConfigurer (Spring Security)
|
|
|
|
|
.webAppContextSetup(context, springSecurity())
|
|
|
|
|
// for illustration only - defaults to ""
|
|
|
|
|
.contextPath("")
|
|
|
|
|
// By default MockMvc is used for localhost only;
|
|
|
|
|
// the following will use MockMvc for example.com and example.org as well
|
|
|
|
|
.useMockMvcForHosts("example.com","example.org")
|
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
----
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
As an alternative, we can perform the exact same setup by configuring the `MockMvc`
|
2018-08-30 23:29:17 +08:00
|
|
|
|
instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder`, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
2017-11-21 05:28:00 +08:00
|
|
|
|
MockMvc mockMvc = MockMvcBuilders
|
|
|
|
|
.webAppContextSetup(context)
|
|
|
|
|
.apply(springSecurity())
|
|
|
|
|
.build();
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2017-11-21 05:28:00 +08:00
|
|
|
|
driver = MockMvcHtmlUnitDriverBuilder
|
|
|
|
|
.mockMvcSetup(mockMvc)
|
|
|
|
|
// for illustration only - defaults to ""
|
|
|
|
|
.contextPath("")
|
|
|
|
|
// By default MockMvc is used for localhost only;
|
|
|
|
|
// the following will use MockMvc for example.com and example.org as well
|
|
|
|
|
.useMockMvcForHosts("example.com","example.org")
|
|
|
|
|
.build();
|
2015-06-24 00:31:48 +08:00
|
|
|
|
----
|
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
This is more verbose, but, by building the `WebDriver` with a `MockMvc` instance, we have
|
2018-08-30 23:29:17 +08:00
|
|
|
|
the full power of MockMvc at our fingertips.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 21:43:14 +08:00
|
|
|
|
TIP: For additional information on creating a `MockMvc` instance, see
|
2015-06-24 00:31:48 +08:00
|
|
|
|
<<spring-mvc-test-server-setup-options>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
[[spring-mvc-test-server-htmlunit-geb]]
|
|
|
|
|
===== MockMvc and Geb
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
In the previous section, we saw how to use MockMvc with WebDriver. In this section, we
|
|
|
|
|
use http://www.gebish.org/[Geb] to make our tests even Groovy-er.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
[[spring-mvc-test-server-htmlunit-geb-why]]
|
|
|
|
|
====== Why Geb and MockMvc?
|
|
|
|
|
|
|
|
|
|
Geb is backed by WebDriver, so it offers many of the
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<spring-mvc-test-server-htmlunit-webdriver-why, same benefits>> that we get from
|
2015-07-29 00:46:18 +08:00
|
|
|
|
WebDriver. However, Geb makes things even easier by taking care of some of the
|
|
|
|
|
boilerplate code for us.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-geb-setup]]
|
|
|
|
|
====== MockMvc and Geb Setup
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
We can easily initialize a Geb `Browser` with a Selenium WebDriver that uses MockMvc, as
|
|
|
|
|
follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[source,groovy]
|
|
|
|
|
----
|
|
|
|
|
def setup() {
|
|
|
|
|
browser.driver = MockMvcHtmlUnitDriverBuilder
|
2015-07-29 00:46:18 +08:00
|
|
|
|
.webAppContextSetup(context)
|
2015-07-28 04:41:22 +08:00
|
|
|
|
.build()
|
2015-06-24 00:31:48 +08:00
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
|
|
|
|
usage, see <<spring-mvc-test-server-htmlunit-webdriver-advanced-builder>>.
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
This ensures that any URL referencing `localhost` as the server is directed to our
|
|
|
|
|
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
|
2018-08-30 23:29:17 +08:00
|
|
|
|
requested by using a network connection as normal. This lets us easily test the use of
|
2015-07-29 00:46:18 +08:00
|
|
|
|
CDNs.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[[spring-mvc-test-server-htmlunit-geb-usage]]
|
|
|
|
|
====== MockMvc and Geb Usage
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Now we can use Geb as we normally would but without the need to deploy our application to
|
|
|
|
|
a Servlet container. For example, we can request the view to create a message with the
|
|
|
|
|
following:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[source,groovy]
|
|
|
|
|
----
|
|
|
|
|
to CreateMessagePage
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
We can then fill out the form and submit it to create a message, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[source,groovy]
|
|
|
|
|
----
|
|
|
|
|
when:
|
|
|
|
|
form.summary = expectedSummary
|
|
|
|
|
form.text = expectedMessage
|
|
|
|
|
submit.click(ViewMessagePage)
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Any unrecognized method calls or property accesses or references that are not found are
|
2018-09-18 22:42:09 +08:00
|
|
|
|
forwarded to the current page object. This removes a lot of the boilerplate code we
|
|
|
|
|
needed when using WebDriver directly.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2015-07-29 00:46:18 +08:00
|
|
|
|
As with direct WebDriver usage, this improves on the design of our
|
2019-03-05 20:08:34 +08:00
|
|
|
|
<<spring-mvc-test-server-htmlunit-mah-usage, HtmlUnit test>> by using the Page Object
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and
|
|
|
|
|
WebDriver, but it is even easier with Geb. Consider our new Groovy-based
|
|
|
|
|
`CreateMessagePage` implementation:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[source,groovy]
|
|
|
|
|
----
|
|
|
|
|
class CreateMessagePage extends Page {
|
|
|
|
|
static url = 'messages/form'
|
2015-07-29 00:46:18 +08:00
|
|
|
|
static at = { assert title == 'Messages : Create'; true }
|
2015-06-24 00:31:48 +08:00
|
|
|
|
static content = {
|
|
|
|
|
submit { $('input[type=submit]') }
|
|
|
|
|
form { $('form') }
|
|
|
|
|
errors(required:false) { $('label.error, .alert-error')?.text() }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Our `CreateMessagePage` extends `Page`. We do not go over the details of `Page`, but, in
|
|
|
|
|
summary, it contains common functionality for all of our pages. We define a URL in which
|
|
|
|
|
this page can be found. This lets us navigate to the page, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[source,groovy]
|
|
|
|
|
----
|
|
|
|
|
to CreateMessagePage
|
|
|
|
|
----
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
We also have an `at` closure that determines if we are at the specified page. It should
|
|
|
|
|
return `true` if we are on the correct page. This is why we can assert that we are on the
|
|
|
|
|
correct page, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[source,groovy]
|
|
|
|
|
----
|
|
|
|
|
then:
|
|
|
|
|
at CreateMessagePage
|
|
|
|
|
errors.contains('This field is required.')
|
|
|
|
|
----
|
2018-08-30 23:29:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
NOTE: We use an assertion in the closure so that we can determine where things went wrong
|
|
|
|
|
if we were at the wrong page.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
Next, we create a `content` closure that specifies all the areas of interest within the
|
|
|
|
|
page. We can use a
|
|
|
|
|
http://www.gebish.org/manual/current/#the-jquery-ish-navigator-api[jQuery-ish Navigator
|
|
|
|
|
API] to select the content in which we are interested.
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Finally, we can verify that a new message was created successfully, as follows:
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
|
|
|
|
[source,groovy]
|
|
|
|
|
----
|
|
|
|
|
then:
|
|
|
|
|
at ViewMessagePage
|
|
|
|
|
success == 'Successfully created a new message'
|
|
|
|
|
id
|
|
|
|
|
date
|
|
|
|
|
summary == expectedSummary
|
|
|
|
|
message == expectedMessage
|
|
|
|
|
----
|
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
For further details on how to get the most out of Geb, see
|
2015-07-29 00:46:18 +08:00
|
|
|
|
http://www.gebish.org/manual/current/[The Book of Geb] user's manual.
|
|
|
|
|
|
2015-06-24 00:31:48 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[spring-mvc-test-client]]
|
|
|
|
|
==== Client-Side REST Tests
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2015-03-03 18:38:01 +08:00
|
|
|
|
----
|
|
|
|
|
RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
|
2016-02-25 04:23:30 +08:00
|
|
|
|
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
|
|
|
|
|
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2016-01-15 04:15:07 +08:00
|
|
|
|
// Test code that uses the above RestTemplate ...
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
|
|
|
|
mockServer.verify();
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[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()
|
|
|
|
|
----
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
In the preceding example, `MockRestServiceServer` (the central class for client-side REST
|
|
|
|
|
tests) configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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
|
2018-08-30 23:29:17 +08:00
|
|
|
|
`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
|
2016-02-25 04:23:30 +08:00
|
|
|
|
used to verify that all expectations have been satisfied.
|
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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`:
|
2016-02-25 04:23:30 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2016-02-25 04:23:30 +08:00
|
|
|
|
----
|
2016-05-26 21:48:16 +08:00
|
|
|
|
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
|
2016-02-25 04:23:30 +08:00
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
|
.Kotlin
|
|
|
|
|
----
|
|
|
|
|
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()
|
|
|
|
|
----
|
2016-02-25 04:23:30 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Even with unordered requests by default, each request is allowed to execute once only.
|
2016-02-25 04:23:30 +08:00
|
|
|
|
The `expect` method provides an overloaded variant that accepts an `ExpectedCount`
|
2018-08-30 23:29:17 +08:00
|
|
|
|
argument that specifies a count range (for example, `once`, `manyTimes`, `max`, `min`,
|
|
|
|
|
`between`, and so on). The following example uses `times`:
|
2016-02-25 04:23:30 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2016-02-25 04:23:30 +08:00
|
|
|
|
----
|
|
|
|
|
RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
|
|
|
|
|
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
|
2018-08-30 23:29:17 +08:00
|
|
|
|
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
|
|
|
|
|
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());
|
2016-02-25 04:23:30 +08:00
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
mockServer.verify();
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[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()
|
|
|
|
|
----
|
2016-02-25 04:23:30 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
Note that, when `ignoreExpectOrder` is not set (the default), and, therefore, requests
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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.
|
2016-02-25 04:23:30 +08:00
|
|
|
|
|
2018-08-30 23:29:17 +08:00
|
|
|
|
As an alternative to all of the above, the client-side test support also provides a
|
2018-09-18 22:42:09 +08:00
|
|
|
|
`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:
|
2016-01-15 04:15:07 +08:00
|
|
|
|
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
|
.Java
|
2016-01-15 04:15:07 +08:00
|
|
|
|
----
|
|
|
|
|
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
|
|
|
|
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
|
|
|
|
|
|
|
|
|
|
// Test code that uses the above RestTemplate ...
|
|
|
|
|
----
|
2019-08-30 08:35:53 +08:00
|
|
|
|
[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 ...
|
|
|
|
|
----
|
2016-01-15 04:15:07 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[spring-mvc-test-client-static-imports]]
|
|
|
|
|
===== Static Imports
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2018-09-18 22:42:09 +08:00
|
|
|
|
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
|
2018-08-30 23:29:17 +08:00
|
|
|
|
configuration. Check for the support for code completion on static members.
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[spring-mvc-test-client-resources]]
|
|
|
|
|
===== Further Examples of Client-side REST Tests
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
Spring MVC Test's own tests include
|
|
|
|
|
https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/client/samples[example
|
|
|
|
|
tests] of client-side REST tests.
|
|
|
|
|
|
2017-10-03 21:56:13 +08:00
|
|
|
|
include::testing-webtestclient.adoc[leveloffset=+2]
|
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2017-10-19 02:24:17 +08:00
|
|
|
|
|
2015-03-03 18:38:01 +08:00
|
|
|
|
[[testing-resources]]
|
|
|
|
|
== Further Resources
|
2018-08-30 23:29:17 +08:00
|
|
|
|
See the following resources for more information about testing:
|
2015-03-03 18:38:01 +08:00
|
|
|
|
|
2019-08-31 20:47:49 +08:00
|
|
|
|
* https://www.junit.org/[JUnit]: "`A programmer-friendly testing framework for Java`".
|
|
|
|
|
Used by the Spring Framework in its test suite and supported in the
|
|
|
|
|
<<testcontext-framework, Spring TestContext Framework>>.
|
2019-03-23 18:54:11 +08:00
|
|
|
|
* https://testng.org/[TestNG]: A testing framework inspired by JUnit with added support
|
2019-08-31 20:47:49 +08:00
|
|
|
|
for test groups, data-driven testing, distributed testing, and other features. Supported
|
|
|
|
|
in the <<testcontext-framework, Spring TestContext Framework>>
|
2019-03-21 06:48:14 +08:00
|
|
|
|
* https://joel-costigliola.github.io/assertj/[AssertJ]: "`Fluent assertions for Java`",
|
2018-08-30 23:29:17 +08:00
|
|
|
|
including support for Java 8 lambdas, streams, and other features.
|
2019-03-21 06:48:14 +08:00
|
|
|
|
* https://en.wikipedia.org/wiki/Mock_Object[Mock Objects]: Article in Wikipedia.
|
2015-03-03 18:38:01 +08:00
|
|
|
|
* http://www.mockobjects.com/[MockObjects.com]: Web site dedicated to mock objects, a
|
|
|
|
|
technique for improving the design of code within test-driven development.
|
2019-03-21 06:48:14 +08:00
|
|
|
|
* https://mockito.github.io[Mockito]: Java mock library based on the
|
2019-08-31 20:47:49 +08:00
|
|
|
|
http://xunitpatterns.com/Test%20Spy.html[Test Spy] pattern. Used by the Spring Framework
|
|
|
|
|
in its test suite.
|
2019-03-23 18:54:11 +08:00
|
|
|
|
* https://easymock.org/[EasyMock]: Java library "`that provides Mock Objects for
|
2019-08-31 20:47:49 +08:00
|
|
|
|
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.
|
2020-03-24 03:05:58 +08:00
|
|
|
|
* 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.
|
2019-03-23 18:54:11 +08:00
|
|
|
|
* https://sourceforge.net/projects/grinder/[The Grinder]: Java load testing framework.
|
2019-07-17 22:37:27 +08:00
|
|
|
|
* https://github.com/Ninja-Squad/springmockk[SpringMockK]: Support for Spring Boot
|
|
|
|
|
integration tests written in Kotlin using https://mockk.io/[MockK] instead of Mockito.
|