diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index 83498510d8..ac26cd7c29 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -446,10 +446,20 @@ classes can be component classes, and so on. The following example shows a `@ContextConfiguration` annotation that refers to an XML file: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @ContextConfiguration("/test-config.xml") <1> + @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 +---- + @ContextConfiguration("/test-config.xml") // <1> class XmlApplicationContextTests { // class body... } @@ -459,10 +469,20 @@ file: The following example shows a `@ContextConfiguration` annotation that refers to a class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @ContextConfiguration(classes = TestConfig.class) <1> + @ContextConfiguration(classes = TestConfig.class) // <1> + class ConfigClassApplicationContextTests { + // class body... + } +---- +<1> Referring to a class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration(classes = [TestConfig::class]) // <1> class ConfigClassApplicationContextTests { // class body... } @@ -474,10 +494,19 @@ As an alternative or in addition to declaring resource locations or annotated cl you can use `@ContextConfiguration` to declare `ApplicationContextInitializer` classes. The following example shows such a case: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @ContextConfiguration(initializers = CustomContextIntializer.class) <1> + @ContextConfiguration(initializers = CustomContextIntializer.class) // <1> + class ContextInitializerTests { + // class body... + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration(initializers = [CustomContextIntializer::class]) // <1> class ContextInitializerTests { // class body... } @@ -492,10 +521,20 @@ annotated `classes`. The following example uses both a location and a loader: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) <1> + @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> class CustomLoaderXmlApplicationContextTests { // class body... } @@ -523,11 +562,21 @@ resource base path). The resource base path is used behind the scenes to create The following example shows how to use the `@WebAppConfiguration` annotation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @WebAppConfiguration <1> + @WebAppConfiguration // <1> + class WebAppTests { + // class body... + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @WebAppConfiguration // <1> class WebAppTests { // class body... } @@ -540,11 +589,22 @@ 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @WebAppConfiguration("classpath:test-web-resources") <1> + @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 +---- + @ContextConfiguration + @WebAppConfiguration("classpath:test-web-resources") // <1> class WebAppTests { // class body... } @@ -568,8 +628,8 @@ defines a level in the context hierarchy. The following examples demonstrate the `@ContextHierarchy` within a single test class (`@ContextHierarchy` can also be used within a test class hierarchy): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextHierarchy({ @ContextConfiguration("/parent-config.xml"), @@ -579,9 +639,19 @@ within a test class hierarchy): // class body... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextHierarchy( + ContextConfiguration("/parent-config.xml"), + ContextConfiguration("/child-config.xml")) + class ContextHierarchyTests { + // class body... + } +---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @WebAppConfiguration @ContextHierarchy({ @@ -592,6 +662,17 @@ within a test class hierarchy): // class body... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @WebAppConfiguration + @ContextHierarchy( + ContextConfiguration(classes = [AppConfig::class]), + ContextConfiguration(classes = [WebConfig::class])) + class WebIntegrationTests { + // class body... + } +---- If you need to merge or override the configuration for a given level of the context hierarchy within a test class hierarchy, you must explicitly name that level by supplying @@ -609,11 +690,22 @@ integration test. The following example indicates that the `dev` profile should be active: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @ActiveProfiles("dev") <1> + @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 +---- + @ContextConfiguration + @ActiveProfiles("dev") // <1> class DeveloperTests { // class body... } @@ -624,11 +716,22 @@ The following example indicates that the `dev` profile should be active: The following example indicates that both the `dev` and the `integration` profiles should be active: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @ActiveProfiles({"dev", "integration"}) <1> + @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> class DeveloperIntegrationTests { // class body... } @@ -663,11 +766,22 @@ properties loaded from resource locations. The following example demonstrates how to declare a properties file from the classpath: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @TestPropertySource("/test.properties") <1> + @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> class MyIntegrationTests { // class body... } @@ -677,11 +791,22 @@ The following example demonstrates how to declare a properties file from the cla The following example demonstrates how to declare inlined properties: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) <1> + @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 +---- + @ContextConfiguration + @TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1> class MyIntegrationTests { // class body... } @@ -712,24 +837,43 @@ configuration scenarios: * Before the current test class, when declared on a class with class mode set to `BEFORE_CLASS`. + -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @DirtiesContext(classMode = BEFORE_CLASS) <1> + @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> class FreshContextTests { // some tests that require a new Spring container } ---- <1> Dirty the context before the current test class. - * After the current test class, when declared on a class with class mode set to `AFTER_CLASS` (i.e., the default class mode). + -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @DirtiesContext <1> + @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> class ContextDirtyingTests { // some tests that result in the Spring container being dirtied } @@ -740,10 +884,20 @@ configuration scenarios: * Before each test method in the current test class, when declared on a class with class mode set to `BEFORE_EACH_TEST_METHOD.` + -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) <1> + @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> class FreshContextTests { // some tests that require a new Spring container } @@ -754,10 +908,20 @@ mode set to `BEFORE_EACH_TEST_METHOD.` * After each test method in the current test class, when declared on a class with class mode set to `AFTER_EACH_TEST_METHOD.` + -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) <1> + @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> class ContextDirtyingTests { // some tests that result in the Spring container being dirtied } @@ -768,31 +932,52 @@ mode set to `AFTER_EACH_TEST_METHOD.` * Before the current test, when declared on a method with the method mode set to `BEFORE_METHOD`. + -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @DirtiesContext(methodMode = BEFORE_METHOD) <1> + @DirtiesContext(methodMode = BEFORE_METHOD) // <1> @Test void testProcessWhichRequiresFreshAppCtx() { // some logic that requires a new Spring container } ---- <1> Dirty the context before the current test method. - ++ +[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. * After the current test, when declared on a method with the method mode set to `AFTER_METHOD` (i.e., the default method mode). + -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @DirtiesContext <1> + @DirtiesContext // <1> @Test void testProcessWhichDirtiesAppCtx() { // some logic that results in the Spring container being dirtied } ---- <1> Dirty the context after the current test method. ++ +[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. If you use `@DirtiesContext` in a test whose context is configured as part of a context @@ -805,8 +990,8 @@ context are removed from the context cache and closed. If the exhaustive algorit overkill for a particular use case, you can specify the simpler current level algorithm, as the following example shows. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextHierarchy({ @ContextConfiguration("/parent-config.xml"), @@ -819,7 +1004,7 @@ as the following example shows. class ExtendedTests extends BaseTests { @Test - @DirtiesContext(hierarchyMode = CURRENT_LEVEL) <1> + @DirtiesContext(hierarchyMode = CURRENT_LEVEL) // <1> void test() { // some logic that results in the child context being dirtied } @@ -827,6 +1012,27 @@ as the following example shows. ---- <1> Use the current-level algorithm. +[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. + For further details regarding the `EXHAUSTIVE` and `CURRENT_LEVEL` algorithms, see the {api-spring-framework}/test/annotation/DirtiesContext.HierarchyMode.html[`DirtiesContext.HierarchyMode`] @@ -842,11 +1048,22 @@ javadoc. The following example shows how to register two `TestExecutionListener` implementations: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) <1> + @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 +---- + @ContextConfiguration + @TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) // <1> class CustomTestExecutionListenerTests { // class body... } @@ -869,10 +1086,10 @@ annotation. The following example shows how to use the `@Commit` annotation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @Commit <1> + @Commit // <1> @Test void testProcessWithoutRollback() { // ... @@ -880,6 +1097,17 @@ The following example shows how to use the `@Commit` annotation: ---- <1> Commit the result of the test to the database. +[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. + [[spring-testing-annotation-rollback]] ===== `@Rollback` @@ -898,10 +1126,10 @@ method, potentially overriding class-level `@Rollback` or `@Commit` semantics. The following example causes a test method's result to not be rolled back (that is, the result is committed to the database): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @Rollback(false) <1> + @Rollback(false) // <1> @Test void testProcessWithoutRollback() { // ... @@ -909,6 +1137,17 @@ result is committed to the database): ---- <1> Do not roll back the result. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Rollback(false) // <1> + @Test + fun testProcessWithoutRollback() { + // ... + } +---- +<1> Do not roll back the result. + [[spring-testing-annotation-beforetransaction]] ===== `@BeforeTransaction` @@ -921,16 +1160,26 @@ transaction by using Spring's `@Transactional` annotation. As of Spring Framewor The following example shows how to use the `@BeforeTransaction` annotation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @BeforeTransaction <1> + @BeforeTransaction // <1> void beforeTransaction() { // logic to be executed before a transaction is started } ---- <1> Run this method before a transaction. +[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. + [[spring-testing-annotation-aftertransaction]] ===== `@AfterTransaction` @@ -941,16 +1190,26 @@ transaction by using Spring's `@Transactional` annotation. As of Spring Framewor `@AfterTransaction` methods are not required to be `public` and may be declared on Java 8-based interface default methods. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @AfterTransaction <1> + @AfterTransaction // <1> void afterTransaction() { // logic to be executed after a transaction has ended } ---- <1> Run this method after a transaction. +[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. + [[spring-testing-annotation-sql]] ===== `@Sql` @@ -959,17 +1218,28 @@ transaction by using Spring's `@Transactional` annotation. As of Spring Framewor against a given database during integration tests. The following example shows how to use it: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Test - @Sql({"/test-schema.sql", "/test-user-data.sql"}) <1> + @Sql({"/test-schema.sql", "/test-user-data.sql"}) // <1> void userTest() { // execute code that relies on the test schema and test data } ---- <1> Run two scripts for this test. +[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. + See <> for further details. @@ -979,13 +1249,13 @@ See <> for further details. `@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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Test @Sql( scripts = "/test-user-data.sql", - config = @SqlConfig(commentPrefix = "`", separator = "@@") <1> + config = @SqlConfig(commentPrefix = "`", separator = "@@") // <1> ) void userTest() { // execute code that relies on the test data @@ -993,6 +1263,16 @@ configured with the `@Sql` annotation. The following example shows how to use it ---- <1> Set the comment prefix and the separator in SQL scripts. +[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. [[spring-testing-annotation-sqlmergemode]] ===== `@SqlMergeMode` @@ -1007,12 +1287,12 @@ Note that a method-level `@SqlMergeMode` declaration overrides a class-level dec The following example shows how to use `@SqlMergeMode` at the class level. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig(TestConfig.class) @Sql("/test-schema.sql") - @SqlMergeMode(MERGE) <1> + @SqlMergeMode(MERGE) // <1> class UserTests { @Test @@ -1024,10 +1304,27 @@ The following example shows how to use `@SqlMergeMode` at the class level. ---- <1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class. +[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. + The following example shows how to use `@SqlMergeMode` at the method level. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig(TestConfig.class) @Sql("/test-schema.sql") @@ -1035,7 +1332,7 @@ The following example shows how to use `@SqlMergeMode` at the method level. @Test @Sql("/user-test-data-001.sql") - @SqlMergeMode(MERGE) <1> + @SqlMergeMode(MERGE) // <1> void standardUserProfile() { // execute code that relies on test data set 001 } @@ -1043,6 +1340,23 @@ The following example shows how to use `@SqlMergeMode` at the method level. ---- <1> Set the `@Sql` merge mode to `MERGE` for a specific test method. +[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. + [[spring-testing-annotation-sqlgroup]] ===== `@SqlGroup` @@ -1053,11 +1367,11 @@ in conjunction with Java 8's support for repeatable annotations, where `@Sql` ca declared several times on the same class or method, implicitly generating this container annotation. The following example shows how to declare an SQL group: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Test - @SqlGroup({ <1> + @SqlGroup({ // <1> @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")), @Sql("/test-user-data.sql") )} @@ -1067,6 +1381,18 @@ annotation. The following example shows how to declare an SQL group: ---- <1> Declare a group of SQL scripts. +[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. [[integration-testing-annotations-standard]] ==== Standard Annotation Support @@ -1134,10 +1460,10 @@ means the test is implicitly enabled. This is analogous to the semantics of JUni The following example shows a test that has an `@IfProfileValue` annotation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @IfProfileValue(name="java.vendor", value="Oracle Corporation") <1> + @IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1> @Test public void testProcessWhichRunsOnlyOnOracleJvm() { // some logic that should run only on Java VMs from Oracle Corporation @@ -1145,15 +1471,26 @@ The following example shows a test that has an `@IfProfileValue` annotation: ---- <1> Run this test only when the Java vendor is "Oracle Corporation". +[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". + 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. Consider the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) <1> + @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) // <1> @Test public void testProcessWhichRunsForUnitOrIntegrationTestGroups() { // some logic that should run only for unit and integration test groups @@ -1161,6 +1498,17 @@ Consider the following example: ---- <1> Run this test for unit tests and integration tests. +[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. + [[integration-testing-annotations-junit4-profilevaluesourceconfiguration]] ===== `@ProfileValueSourceConfiguration` @@ -1171,16 +1519,26 @@ of `ProfileValueSource` to use when retrieving profile values configured through test, `SystemProfileValueSource` is used by default. The following example shows how to use `@ProfileValueSourceConfiguration`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @ProfileValueSourceConfiguration(CustomProfileValueSource.class) <1> + @ProfileValueSourceConfiguration(CustomProfileValueSource.class) // <1> public class CustomProfileValueSourceTests { // class body... } ---- <1> Use a custom profile value source. +[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. + [[integration-testing-annotations-junit4-timed]] ===== `@Timed` @@ -1193,16 +1551,26 @@ The time period includes running the test method itself, any repetitions of the `@Repeat`), as well as any setting up or tearing down of the test fixture. The following example shows how to use it: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @Timed(millis = 1000) <1> + @Timed(millis = 1000) // <1> public void testProcessWithOneSecondTimeout() { // some logic that should not take longer than 1 second to execute } ---- <1> Set the time period for the test to one second. +[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. + 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 @@ -1221,10 +1589,10 @@ The scope of execution to be repeated includes execution of the test method itse well as any setting up or tearing down of the test fixture. The following example shows how to use the `@Repeat` annotation: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @Repeat(10) <1> + @Repeat(10) // <1> @Test public void testProcessRepeatedly() { // ... @@ -1232,6 +1600,17 @@ how to use the `@Repeat` annotation: ---- <1> Repeat this test ten times. +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Repeat(10) // <1> + @Test + fun testProcessRepeatedly() { + // ... + } +---- +<1> Repeat this test ten times. + [[integration-testing-annotations-junit-jupiter]] @@ -1260,10 +1639,20 @@ classes may be declared with the `value` attribute in `@SpringJUnitConfig`. The following example shows how to use the `@SpringJUnitConfig` annotation to specify a configuration class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @SpringJUnitConfig(TestConfig.class) <1> + @SpringJUnitConfig(TestConfig.class) // <1> + class ConfigurationClassJUnitJupiterSpringTests { + // class body... + } +---- +<1> Specify the configuration class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) // <1> class ConfigurationClassJUnitJupiterSpringTests { // class body... } @@ -1274,10 +1663,20 @@ configuration class: The following example shows how to use the `@SpringJUnitConfig` annotation to specify the location of a configuration file: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @SpringJUnitConfig(locations = "/test-config.xml") <1> + @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 +---- + @SpringJUnitConfig(locations = ["/test-config.xml"]) // <1> class XmlJUnitJupiterSpringTests { // class body... } @@ -1305,10 +1704,20 @@ attribute from `@WebAppConfiguration` only by using the `resourcePath` attribute The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify a configuration class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @SpringJUnitWebConfig(TestConfig.class) <1> + @SpringJUnitWebConfig(TestConfig.class) // <1> + class ConfigurationClassJUnitJupiterSpringWebTests { + // class body... + } +---- +<1> Specify the configuration class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig(TestConfig::class) // <1> class ConfigurationClassJUnitJupiterSpringWebTests { // class body... } @@ -1319,10 +1728,20 @@ a configuration class: The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify a the location of a configuration file: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @SpringJUnitWebConfig(locations = "/test-config.xml") <1> + @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 +---- + @SpringJUnitWebConfig(locations = ["/test-config.xml"]) // <1> class XmlJUnitJupiterSpringWebTests { // class body... } @@ -1389,8 +1808,8 @@ equivalent to `@Disabled` and `@EnabledIf("true")` is logically meaningless. You can use `@EnabledIf` as a meta-annotation to create custom composed annotations. For example, you can create a custom `@EnabledOnMac` annotation as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @@ -1400,6 +1819,17 @@ example, you can create a custom `@EnabledOnMac` annotation as follows: ) public @interface EnabledOnMac {} ---- +[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 {} +---- [[integration-testing-annotations-junit-jupiter-disabledif]] ===== `@DisabledIf` @@ -1425,8 +1855,8 @@ equivalent to `@Disabled` and `@DisabledIf("false")` is logically meaningless. You can use `@DisabledIf` as a meta-annotation to create custom composed annotations. For example, you can create a custom `@DisabledOnMac` annotation as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @@ -1437,6 +1867,17 @@ example, you can create a custom `@DisabledOnMac` annotation as follows: public @interface DisabledOnMac {} ---- +[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 {} +---- [[integration-testing-annotations-meta]] ==== Meta-Annotation Support for Testing @@ -1477,8 +1918,8 @@ You can use each of the following as a meta-annotation in conjunction with the Consider the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RunWith(SpringRunner.class) @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) @@ -1493,12 +1934,28 @@ Consider the following example: public class UserRepositoryTests { } ---- +[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 { } +---- + 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -1508,11 +1965,22 @@ that centralizes the common test configuration for Spring, as follows: public @interface TransactionalDevTestConfig { } ---- +[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 { } +---- + Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the configuration of individual JUnit 4 based test classes, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RunWith(SpringRunner.class) @TransactionalDevTestConfig @@ -1523,12 +1991,24 @@ configuration of individual JUnit 4 based test classes, as follows: public class UserRepositoryTests { } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RunWith(SpringRunner::class) + @TransactionalDevTestConfig + class OrderRepositoryTests + + @RunWith(SpringRunner::class) + @TransactionalDevTestConfig + class UserRepositoryTests +---- + If we write tests that use JUnit Jupiter, we can reduce code duplication even further, since annotations in JUnit 5 can also be used as meta-annotations. Consider the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) @@ -1542,14 +2022,29 @@ example: @Transactional class UserRepositoryTests { } ---- +[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 { } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -1559,12 +2054,32 @@ as follows: @Transactional public @interface TransactionalDevTestConfig { } ---- +[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 { } +---- Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the configuration of individual JUnit Jupiter based test classes, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[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 ---- @TransactionalDevTestConfig class OrderRepositoryTests { } @@ -1580,8 +2095,8 @@ the `@Test` and `@Tag` annotations from JUnit Jupiter with the `@Transactional` annotation from Spring, we could create an `@TransactionalIntegrationTest` annotation, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -1590,12 +2105,22 @@ follows: @Test // org.junit.jupiter.api.Test public @interface TransactionalIntegrationTest { } ---- +[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 { } +---- Then we can use our custom `@TransactionalIntegrationTest` annotation to simplify the configuration of individual JUnit Jupiter based test methods, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @TransactionalIntegrationTest void saveOrder() { } @@ -1604,6 +2129,16 @@ configuration of individual JUnit Jupiter based test methods, as follows: void deleteOrder() { } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @TransactionalIntegrationTest + fun saveOrder() { } + + @TransactionalIntegrationTest + fun deleteOrder() { } +---- + For further details, see the https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[Spring Annotation Programming Model] wiki page. @@ -1814,8 +2349,8 @@ default listeners are not registered. In most common testing scenarios, this eff forces the developer to manually declare all default listeners in addition to any custom listeners. The following listing demonstrates this style of configuration: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration @TestExecutionListeners({ @@ -1832,6 +2367,24 @@ listeners. The following listing demonstrates this style of configuration: } ---- +[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... + } +---- + 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 @@ -1858,8 +2411,8 @@ configures its `order` value (for example, `500`) to be less than the order of t defaults in front of the `ServletTestExecutionListener`, and the previous example could be replaced with the following: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration @TestExecutionListeners( @@ -1870,6 +2423,18 @@ be replaced with the following: // class body... } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestExecutionListeners( + listeners = [MyCustomTestExecutionListener::class], + mergeMode = MERGE_WITH_DEFAULTS + ) + class MyTest { + // class body... + } +---- [[testcontext-test-execution-events]] ==== Test Execution Events @@ -1953,13 +2518,13 @@ As an alternative to implementing the `ApplicationContextAware` interface, you c the application context for your test class through the `@Autowired` annotation on either a field or setter method, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig class MyTest { - @Autowired <1> + @Autowired // <1> ApplicationContext applicationContext; // class body... @@ -1967,17 +2532,31 @@ a field or setter method, as the following example shows: ---- <1> Injecting the `ApplicationContext`. +[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`. + Similarly, if your test is configured to load a `WebApplicationContext`, you can inject the web application context into your test, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - @SpringJUnitWebConfig <1> + @SpringJUnitWebConfig // <1> class MyWebAppTest { - @Autowired <2> + @Autowired // <2> WebApplicationContext wac; // class body... @@ -1986,6 +2565,20 @@ the web application context into your test, as follows: <1> Configuring the `WebApplicationContext`. <2> Injecting the `WebApplicationContext`. +[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`. + Dependency injection by using `@Autowired` is provided by the `DependencyInjectionTestExecutionListener`, which is configured by default @@ -2031,13 +2624,26 @@ is treated as an absolute classpath location (for example, `/org/example/config. path that represents a resource URL (i.e., a path prefixed with `classpath:`, `file:`, `http:`, etc.) is used _as is_. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from "/app-config.xml" and // "/test-config.xml" in the root of the classpath - @ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) <1> + @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> class MyTest { // class body... } @@ -2051,8 +2657,8 @@ attributes in `@ContextConfiguration`, you can omit the declaration of the `loca attribute name and declare the resource locations by using the shorthand format demonstrated in the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @ContextConfiguration({"/app-config.xml", "/test-config.xml"}) <1> @@ -2062,6 +2668,17 @@ demonstrated in the following example: ---- <1> Specifying XML files without using the `location` attribute. +[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. + If you omit both the `locations` and the `value` attributes from the `@ContextConfiguration` annotation, the TestContext framework tries to detect a default @@ -2071,15 +2688,26 @@ class. If your class is named `com.example.MyTest`, `GenericXmlContextLoader` lo application context from `"classpath:com/example/MyTest-context.xml"`. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - package com.example; - @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from // "classpath:com/example/MyTest-context.xml" - @ContextConfiguration <1> + @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> class MyTest { // class body... } @@ -2103,8 +2731,8 @@ TestContext Framework is enabled automatically if Groovy is on the classpath. The following example shows how to specify Groovy configuration files: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from "/AppConfig.groovy" and @@ -2114,6 +2742,18 @@ The following example shows how to specify Groovy configuration files: // class body... } ---- + +[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... + } +---- <1> Specifying the location of Groovy configuration files. @@ -2125,15 +2765,26 @@ detect a default location based on the name of the test class. If your class is `"classpath:com/example/MyTestContext.groovy"`. The following example shows how to use the default: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - package com.example; - @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from // "classpath:com/example/MyTestContext.groovy" - @ContextConfiguration <1> + @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> class MyTest { // class body... } @@ -2152,8 +2803,8 @@ configured resource location ends with `.xml`, it is loaded by using an The following listing shows how to combine both in an integration test: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from @@ -2163,6 +2814,17 @@ The following listing shows how to combine both in an integration test: // class body... } ---- +[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... + } +---- ===== [[testcontext-ctx-management-javaconfig]] @@ -2173,12 +2835,24 @@ To load an `ApplicationContext` for your tests by using annotated classes (see class with `@ContextConfiguration` and configure the `classes` attribute with an array that contains references to annotated classes. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[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> + @ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) // <1> + class MyTest { + // class body... + } +---- +<1> Specifying annotated classes. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from AppConfig and TestConfig + @ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) // <1> class MyTest { // class body... } @@ -2215,8 +2889,8 @@ example, the `OrderServiceTest` class declares a `static` nested configuration c named `Config` that is automatically used to load the `ApplicationContext` for the test class: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig <1> // ApplicationContext will be loaded from the @@ -2247,6 +2921,35 @@ class: ---- <1> Loading configuration information from the nested `Config` class. +[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. + [[testcontext-ctx-management-mixed-config]] ===== Mixing XML, Groovy Scripts, and Annotated Classes @@ -2296,15 +2999,30 @@ order in which the initializers are invoked depends on whether they implement Sp `Ordered` interface or are annotated with Spring's `@Order` annotation or the standard `@Priority` annotation. The following example shows how to use initializers: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from TestConfig // and initialized by TestAppCtxInitializer @ContextConfiguration( classes = TestConfig.class, - initializers = TestAppCtxInitializer.class) <1> + 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> class MyTest { // class body... } @@ -2318,8 +3036,8 @@ annotated classes in `@ContextConfiguration` entirely and instead declare only in the context -- for example, by programmatically loading bean definitions from XML files or configuration classes. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be initialized by EntireAppInitializer @@ -2331,6 +3049,19 @@ files or configuration classes. The following example shows how to do so: ---- <1> Specifying configuration by using only an initializer. +[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. + [[testcontext-ctx-management-inheritance]] ===== Context Configuration Inheritance @@ -2357,8 +3088,8 @@ Beans defined in `extended-config.xml` can, therefore, override (that is, replac 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from "/base-config.xml" @@ -2378,6 +3109,27 @@ another and use both its own configuration file and the superclass's configurati <1> Configuration file defined in the superclass. <2> Configuration file defined in the subclass. +[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. + Similarly, in the next example, which uses annotated classes, the `ApplicationContext` for `ExtendedTest` is loaded from the `BaseConfig` and `ExtendedConfig` classes, in that @@ -2385,17 +3137,17 @@ order. Beans defined in `ExtendedConfig` can, therefore, override (that is, repl 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // ApplicationContext will be loaded from BaseConfig - @SpringJUnitConfig(BaseConfig.class) <1> + @SpringJUnitConfig(BaseConfig.class) // <1> class BaseTest { // class body... } // ApplicationContext will be loaded from BaseConfig and ExtendedConfig - @SpringJUnitConfig(ExtendedConfig.class) <2> + @SpringJUnitConfig(ExtendedConfig.class) // <2> class ExtendedTest extends BaseTest { // class body... } @@ -2403,6 +3155,24 @@ another and use both its own configuration class and the superclass's configurat <1> Configuration class defined in the superclass. <2> Configuration class defined in the subclass. +[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. + In the next example, which uses context initializers, the `ApplicationContext` for `ExtendedTest` is initialized by using `BaseInitializer` and `ExtendedInitializer`. Note, @@ -2411,18 +3181,18 @@ implement Spring's `Ordered` interface or are annotated with Spring's `@Order` a 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // ApplicationContext will be initialized by BaseInitializer - @SpringJUnitConfig(initializers = BaseInitializer.class) <1> + @SpringJUnitConfig(initializers = BaseInitializer.class) // <1> class BaseTest { // class body... } // ApplicationContext will be initialized by BaseInitializer // and ExtendedInitializer - @SpringJUnitConfig(initializers = ExtendedInitializer.class) <2> + @SpringJUnitConfig(initializers = ExtendedInitializer.class) // <2> class ExtendedTest extends BaseTest { // class body... } @@ -2430,6 +3200,25 @@ extend another and use both its own initializer and the superclass's initializer <1> Initializer defined in the superclass. <2> Initializer defined in the subclass. +[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. + [[testcontext-ctx-management-env-profiles]] ===== Context Configuration with Environment Profiles @@ -2447,8 +3236,7 @@ SPI, but `@ActiveProfiles` is not supported with implementations of the older Consider two examples with XML configuration and `@Configuration` classes: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - package com.bank.service; - @ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from "classpath:/app-config.xml" @ContextConfiguration("/app-config.xml") @@ -2514,6 +3300,24 @@ Consider two examples with XML configuration and `@Configuration` classes: } } ---- +[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 + } + } +---- When `TransferServiceTest` is run, its `ApplicationContext` is loaded from the `app-config.xml` configuration file in the root of the classpath. If you inspect @@ -2537,8 +3341,8 @@ but define an in-memory data source as a default when neither of these is active The following code listings demonstrate how to implement the same configuration and integration test with `@Configuration` classes instead of XML: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration @Profile("dev") @@ -2554,9 +3358,26 @@ integration test with `@Configuration` classes instead of XML: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @Profile("dev") + class StandaloneDataConfig { -[source,java,indent=0] -[subs="verbatim,quotes"] + @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 ---- @Configuration @Profile("production") @@ -2569,9 +3390,23 @@ integration test with `@Configuration` classes instead of XML: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @Profile("production") + class JndiDataConfig { -[source,java,indent=0] -[subs="verbatim,quotes"] + @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 ---- @Configuration @Profile("default") @@ -2586,9 +3421,25 @@ integration test with `@Configuration` classes instead of XML: } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @Profile("default") + class DefaultDataConfig { -[source,java,indent=0] -[subs="verbatim,quotes"] + @Bean + fun dataSource(): DataSource { + return EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .addScript("classpath:com/bank/config/sql/schema.sql") + .build() + } +} +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration public class TransferServiceConfig { @@ -2609,15 +3460,37 @@ integration test with `@Configuration` classes instead of XML: public FeePolicy feePolicy() { return new ZeroFeePolicy(); } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class TransferServiceConfig { + @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() + } } ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - package com.bank.service; - @SpringJUnitConfig({ TransferServiceConfig.class, StandaloneDataConfig.class, @@ -2635,6 +3508,26 @@ integration test with `@Configuration` classes instead of XML: } } ---- +[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 + } + } +---- In this variation, we have split the XML configuration into four independent `@Configuration` classes: @@ -2660,11 +3553,9 @@ automatically inherit the `@ActiveProfiles` configuration from the base class. I following example, the declaration of `@ActiveProfiles` (as well as other annotations) has been moved to an abstract superclass, `AbstractIntegrationTest`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - package com.bank.service; - @SpringJUnitConfig({ TransferServiceConfig.class, StandaloneDataConfig.class, @@ -2675,11 +3566,22 @@ has been moved to an abstract superclass, `AbstractIntegrationTest`: } ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig( + TransferServiceConfig::class, + StandaloneDataConfig::class, + JndiDataConfig::class, + DefaultDataConfig::class) + @ActiveProfiles("dev") + abstract class AbstractIntegrationTest { + } ---- - package com.bank.service; +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- // "dev" profile inherited from superclass class TransferServiceTest extends AbstractIntegrationTest { @@ -2692,15 +3594,28 @@ has been moved to an abstract superclass, `AbstractIntegrationTest`: } } ---- +[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 + } + } +---- `@ActiveProfiles` also supports an `inheritProfiles` attribute that can be used to disable the inheritance of active profiles, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - package com.bank.service; - // "dev" profile overridden with "production" @ActiveProfiles(profiles = "production", inheritProfiles = false) class ProductionTransferServiceTest extends AbstractIntegrationTest { @@ -2708,6 +3623,16 @@ disable the inheritance of active profiles, as the following example shows: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // "dev" profile overridden with "production" + @ActiveProfiles("production", inheritProfiles = false) + class ProductionTransferServiceTest : AbstractIntegrationTest() { + // test body + } +---- + [[testcontext-ctx-management-env-profiles-ActiveProfilesResolver]] Furthermore, it is sometimes necessary to resolve active profiles for tests programmatically instead of declaratively -- for example, based on: @@ -2725,11 +3650,9 @@ attribute of `@ActiveProfiles`. For further information, see the corresponding The following example demonstrates how to implement and register a custom `OperatingSystemActiveProfilesResolver`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- - package com.bank.service; - // "dev" profile overridden programmatically via a custom resolver @ActiveProfiles( resolver = OperatingSystemActiveProfilesResolver.class, @@ -2739,11 +3662,21 @@ The following example demonstrates how to implement and register a custom } ---- -[source,java,indent=0] -[subs="verbatim,quotes"] +[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 + } ---- - package com.bank.service.test; +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver { @Override @@ -2754,6 +3687,18 @@ The following example demonstrates how to implement and register a custom } } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver { + + override fun resolve(testClass: Class<*>): Array { + val profile: String = ... + // determine the value of profile based on the operating system + return arrayOf(profile) + } + } +---- [[testcontext-ctx-management-property-sources]] ===== Context Configuration with Test Property Sources @@ -2797,11 +3742,22 @@ loaded by using the specified resource protocol. Resource location wildcards (su The following example uses a test properties file: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @TestPropertySource("/test.properties") <1> + @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 +---- + @ContextConfiguration + @TestPropertySource("/test.properties") // <1> class MyIntegrationTests { // class body... } @@ -2823,11 +3779,22 @@ a Java properties file: The following example sets two inlined properties: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration - @TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) <1> + @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> class MyIntegrationTests { // class body... } @@ -2878,8 +3845,8 @@ entries for the `timezone` and `port` properties those are overridden by the inl properties declared by using the `properties` attribute. The following example shows how to specify properties both in a file and inline: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration @TestPropertySource( @@ -2891,6 +3858,18 @@ to specify properties both in a file and inline: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestPropertySource("/test.properties", + properties = ["timezone = GMT", "port: 4242"] + ) + class MyIntegrationTests { + // class body... + } +---- + ====== Inheriting and Overriding Test Property Sources `@TestPropertySource` supports boolean `inheritLocations` and `inheritProperties` @@ -2914,8 +3893,8 @@ for `ExtendedTest` is loaded by using the `base.properties` and `extended.proper 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @TestPropertySource("base.properties") @ContextConfiguration @@ -2929,14 +3908,29 @@ properties in both a subclass and its superclass by using `properties` files: // ... } ---- +[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"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @TestPropertySource(properties = "key1 = value1") @ContextConfiguration @@ -2951,6 +3945,22 @@ to define properties in both a subclass and its superclass by using inline prope } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @TestPropertySource(properties = ["key1 = value1"]) + @ContextConfiguration + open class BaseTest { + // ... + } + + @TestPropertySource(properties = ["key2 = value2"]) + @ContextConfiguration + class ExtendedTest : BaseTest() { + // ... + } +---- + [[testcontext-ctx-management-web]] ===== Loading a `WebApplicationContext` @@ -2984,8 +3994,8 @@ loading a `WebApplicationContext`. The following example shows the TestContext framework's support for convention over configuration: .Conventions -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @@ -2995,7 +4005,21 @@ framework's support for convention over configuration: // detects "WacTests-context.xml" in the same package // or static nested @Configuration classes @ContextConfiguration + class WacTests { + //... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // defaults to "file:src/main/webapp" + @WebAppConfiguration + + // detects "WacTests-context.xml" in the same package + // or static nested @Configuration classes + @ContextConfiguration class WacTests { //... } @@ -3012,8 +4036,8 @@ The following example shows how to explicitly declare a resource base path with `@WebAppConfiguration` and an XML resource location with `@ContextConfiguration`: .Default resource semantics -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @@ -3027,6 +4051,20 @@ The following example shows how to explicitly declare a resource base path with //... } ---- +[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 { + //... + } +---- 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, @@ -3036,8 +4074,8 @@ The following example shows that we can override the default resource semantics annotations by specifying a Spring resource prefix: .Explicit resource semantics -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @@ -3051,6 +4089,20 @@ annotations by specifying a Spring resource prefix: //... } ---- +[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 { + //... + } +---- Contrast the comments in this example with the previous example. @@ -3074,8 +4126,8 @@ mocks can be autowired into your test instance. Note that the `WebApplicationCon managed per test method by the `ServletTestExecutionListener`. .Injecting mocks -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitWebConfig class WacTests { @@ -3101,6 +4153,34 @@ managed per test method by the `ServletTestExecutionListener`. //... } ---- + +[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 + + //... + } +---- -- [[testcontext-ctx-management-caching]] @@ -3219,8 +4299,8 @@ one for the root `WebApplicationContext` (loaded by using the `TestAppConfig` that is autowired into the test instance is the one for the child context (that is, the lowest context in the hierarchy). The following listing shows this configuration scenario: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @WebAppConfiguration @@ -3236,6 +4316,23 @@ lowest context in the hierarchy). The following listing shows this configuration // ... } ---- + +[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 + + // ... + } +---- -- @@ -3254,8 +4351,8 @@ based on the configuration in `AbstractWebTests` is set as the parent context fo the contexts loaded for the concrete subclasses. The following listing shows this configuration scenario: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @WebAppConfiguration @@ -3268,8 +4365,22 @@ configuration scenario: @ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml")) public class RestWebServiceTests extends AbstractWebTests {} ---- --- +[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 + @ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml")) + class SoapWebServiceTests : AbstractWebTests() + + @ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml")) + class RestWebServiceTests : AbstractWebTests() + +---- +-- .Class hierarchy with merged context hierarchy configuration -- @@ -3285,8 +4396,8 @@ application context loaded from `/app-config.xml` is set as the parent context f contexts loaded from `/user-config.xml` and `{"/user-config.xml", "/order-config.xml"}`. The following listing shows this configuration scenario: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @ContextHierarchy({ @@ -3300,6 +4411,21 @@ The following listing shows this configuration scenario: ) class ExtendedTests extends BaseTests {} ---- + +[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() {} +---- -- .Class hierarchy with overridden context hierarchy configuration @@ -3311,8 +4437,8 @@ application context for `ExtendedTests` is loaded only from `/test-user-config.x has its parent set to the context loaded from `/app-config.xml`. The following listing shows this configuration scenario: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) @ContextHierarchy({ @@ -3329,6 +4455,23 @@ shows this configuration scenario: )) class ExtendedTests extends BaseTests {} ---- +[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() {} +---- .Dirtying a context within a context hierarchy NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a @@ -3394,8 +4537,8 @@ example. The first code listing shows a JUnit Jupiter based implementation of the test class that uses `@Autowired` for field injection: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) // specifies the Spring configuration to load for this test fixture @@ -3414,11 +4557,31 @@ uses `@Autowired` for field injection: } ---- +[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) + } + } +---- + Alternatively, you can configure the class to use `@Autowired` for setter injection, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ExtendWith(SpringExtension.class) // specifies the Spring configuration to load for this test fixture @@ -3441,6 +4604,30 @@ follows: } ---- +[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) + } + } +---- + The preceding code listings use the same XML context file referenced by the `@ContextConfiguration` annotation (that is, `repository-config.xml`). The following shows this configuration: @@ -3475,8 +4662,8 @@ such a case, you can override the setter method and use the `@Qualifier` annotat indicate a specific target bean, as follows (but make sure to delegate to the overridden method in the superclass as well): -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // ... @@ -3489,6 +4676,19 @@ method in the superclass as well): // ... ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // ... + + @Autowired + override fun setDataSource(@Qualifier("myDataSource") dataSource: DataSource) { + super.setDataSource(dataSource) + } + + // ... +---- + 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 `` declarations within the corresponding `` definitions. The bean name @@ -3547,8 +4747,8 @@ set parameters). We can then perform assertions against the results based on the inputs for the username and password. The following listing shows how to do so: .Request-scoped bean test -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitWebConfig class RequestScopedBeanTests { @@ -3566,6 +4766,25 @@ inputs for the username and password. The following listing shows how to do so: } } ---- +[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 + } + } +---- 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 @@ -3601,8 +4820,8 @@ the user service has access to the session-scoped `userPreferences` for the curr configured theme. The following example shows how to do so: .Session-scoped bean test -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitWebConfig class SessionScopedBeanTests { @@ -3620,6 +4839,24 @@ configured theme. The following example shows how to do so: } ---- +[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 + } + } +---- [[testcontext-tx]] ==== Transaction Management @@ -3725,8 +4962,8 @@ are preconfigured for transactional support at the class level. The following example demonstrates a common scenario for writing an integration test for a Hibernate-based `UserRepository`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig(TestConfig.class) @Transactional @@ -3768,6 +5005,49 @@ a Hibernate-based `UserRepository`: } ---- +[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")) + } + } +---- + As explained in <>, there is no need to clean up the database after the `createUser()` method runs, since any changes made to the database are automatically rolled back by the `TransactionalTestExecutionListener`. @@ -3794,8 +5074,8 @@ The following example demonstrates some of the features of `TestTransaction`. Se javadoc for {api-spring-framework}/test/context/transaction/TestTransaction.html[`TestTransaction`] for further details. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ContextConfiguration(classes = TestConfig.class) public class ProgrammaticTransactionManagementTests extends @@ -3824,6 +5104,35 @@ for further details. } } ---- +[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")) + } + } +---- [[testcontext-tx-before-and-after-tx]] ===== Running Code Outside of a Transaction @@ -3869,8 +5178,8 @@ Transaction management for `@Sql`>> contains an additional example that uses `@S declarative SQL script execution with default transaction rollback semantics. The following example shows the relevant annotations: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig @Transactional(transactionManager = "txMgr") @@ -3907,6 +5216,44 @@ following example shows the relevant annotations: } ---- +[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 + } + + } +---- + [[testcontext-tx-false-positives]] .Avoid false positives when testing ORM code @@ -3921,8 +5268,8 @@ of work. In the following Hibernate-based example test case, one method demonstr false positive, and the other method correctly exposes the results of flushing the session: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // ... @@ -3948,10 +5295,37 @@ session: // ... ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // ... + + @Autowired + lateinit var sessionFactory: SessionFactory + + @Transactional + @Test // no expected exception! + fun falsePositive() { + updateEntityInHibernateSession() + // False positive: an exception will be thrown once the Hibernate + // Session is finally flushed (i.e., in production code) + } + + @Transactional + @Test(expected = ...) + fun updateWithSessionFlush() { + updateEntityInHibernateSession() + // Manual flush is required to avoid false positive in test + sessionFactory.getCurrentSession().flush() + } + + // ... +---- + The following example shows matching methods for JPA: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // ... @@ -3976,6 +5350,32 @@ The following example shows matching methods for JPA: // ... ---- +[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() + } + + // ... +---- ===== @@ -4027,8 +5427,8 @@ 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`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Test void databaseTest() { @@ -4041,6 +5441,20 @@ specifies SQL scripts for a test schema and test data, sets the statement separa // execute code that uses the test schema and data } ---- +[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 + } +---- Note that `ResourceDatabasePopulator` internally delegates to `ScriptUtils` for parsing and running SQL scripts. Similarly, the `executeSqlScript(..)` methods in @@ -4077,8 +5491,8 @@ the specified resource protocol. 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig @Sql("/test-schema.sql") @@ -4097,6 +5511,26 @@ within a JUnit Jupiter based integration test class: } ---- +[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 + } + } +---- + [[testcontext-executing-sql-declaratively-script-detection]] ====== Default Script Detection @@ -4122,8 +5556,8 @@ Java 8, you can use `@Sql` as a repeatable annotation. Otherwise, you can use th The following example shows how to use `@Sql` as a repeatable annotation with Java 8: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Test @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")) @@ -4132,6 +5566,11 @@ The following example shows how to use `@Sql` as a repeatable annotation with Ja // execute code that uses the test schema and test data } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin +---- In the scenario presented in the preceding example, the `test-schema.sql` script uses a different syntax for single-line comments. @@ -4140,8 +5579,8 @@ The following example is identical to the preceding example, except that the `@S declarations are grouped together within `@SqlGroup`, for compatibility with Java 6 and Java 7. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Test @SqlGroup({ @@ -4152,6 +5591,17 @@ Java 7. // execute code that uses the test schema and test data } ---- +[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 + } +---- [[testcontext-executing-sql-declaratively-script-execution-phases]] ====== Script Execution Phases @@ -4161,8 +5611,8 @@ you need to run a particular set of scripts after the test method (for example, up database state), you can use the `executionPhase` attribute in `@Sql`, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Test @Sql( @@ -4179,6 +5629,11 @@ following example shows: // to the database outside of the test's transaction } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin +---- Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from `Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively. @@ -4234,8 +5689,8 @@ reference manual, the javadoc for provide detailed information, and the following example shows a typical testing scenario that uses JUnit Jupiter and transactional tests with `@Sql`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig(TestDatabaseConfig.class) @Transactional @@ -4266,6 +5721,33 @@ that uses JUnit Jupiter and transactional tests with `@Sql`: } } ---- +[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.") + } + } +---- 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 @@ -4355,8 +5837,8 @@ alternative runner (such as JUnit 4's `Parameterized` runner) or third-party run The following code listing shows the minimal requirements for configuring a test class to run with the custom Spring `Runner`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @RunWith(SpringRunner.class) @TestExecutionListeners({}) @@ -4369,6 +5851,20 @@ run with the custom Spring `Runner`: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RunWith(SpringRunner::class) + @TestExecutionListeners + class SimpleTest { + + @Test + fun testMethod() { + // execute test logic... + } + } +---- + 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`. @@ -4395,8 +5891,8 @@ To support the full functionality of the TestContext framework, you must combine `SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way to declare these rules in an integration test: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Optionally specify a non-Spring Runner via @RunWith(...) @ContextConfiguration @@ -4415,6 +5911,28 @@ to declare these rules in an integration test: } ---- +[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() + } + } +---- + [[testcontext-support-classes-junit4]] ===== JUnit 4 Support Classes @@ -4478,8 +5996,8 @@ TestNG: The following code listing shows how to configure a test class to use the `SpringExtension` in conjunction with `@ContextConfiguration`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Instructs JUnit Jupiter to extend the test with Spring support. @ExtendWith(SpringExtension.class) @@ -4494,6 +6012,22 @@ The following code listing shows how to configure a test class to use the } ---- +[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... + } + } +---- + Since you can also use annotations in JUnit 5 as meta-annotations, Spring provides the `@SpringJUnitConfig` and `@SpringJUnitWebConfig` composed annotations to simplify the configuration of the test `ApplicationContext` and JUnit Jupiter. @@ -4501,8 +6035,8 @@ configuration of the test `ApplicationContext` and JUnit Jupiter. The following example uses `@SpringJUnitConfig` to reduce the amount of configuration used in the previous example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Instructs Spring to register the SpringExtension with JUnit // Jupiter and load an ApplicationContext from TestConfig.class @@ -4516,11 +6050,26 @@ used in the previous example: } ---- +[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... + } + } +---- + Similarly, the following example uses `@SpringJUnitWebConfig` to create a `WebApplicationContext` for use with JUnit Jupiter: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // Instructs Spring to register the SpringExtension with JUnit // Jupiter and load a WebApplicationContext from TestWebConfig.class @@ -4534,6 +6083,21 @@ Similarly, the following example uses `@SpringJUnitWebConfig` to create a } ---- +[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... + } + } +---- + See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in <> for further details. @@ -4598,8 +6162,8 @@ In the following example, Spring injects the `OrderService` bean from the `ApplicationContext` loaded from `TestConfig.class` into the `OrderServiceIntegrationTests` constructor. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig(TestConfig.class) class OrderServiceIntegrationTests { @@ -4608,21 +6172,31 @@ In the following example, Spring injects the `OrderService` bean from the @Autowired OrderServiceIntegrationTests(OrderService orderService) { - this.orderService = orderService. + this.orderService = orderService; } // tests that use the injected OrderService } ---- +[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 + } + +---- + Note that this feature lets test dependencies be `final` and therefore immutable. If the `spring.test.constructor.autowire.mode` property is to `all` (see <>), we can omit the declaration of `@Autowired` on the constructor in the previous example, resulting in the following. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig(TestConfig.class) class OrderServiceIntegrationTests { @@ -4630,13 +6204,22 @@ If the `spring.test.constructor.autowire.mode` property is to `all` (see private final OrderService orderService; OrderServiceIntegrationTests(OrderService orderService) { - this.orderService = orderService. + this.orderService = orderService; } // tests that use the injected OrderService } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + class OrderServiceIntegrationTests(val orderService:OrderService) { + // tests that use the injected OrderService + } +---- + [[testcontext-junit-jupiter-di-method]] ====== Method Injection @@ -4648,8 +6231,8 @@ parameter with the corresponding bean from the test's `ApplicationContext`. In the following example, Spring injects the `OrderService` from the `ApplicationContext` loaded from `TestConfig.class` into the `deleteOrder()` test method: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig(TestConfig.class) class OrderServiceIntegrationTests { @@ -4661,6 +6244,19 @@ loaded from `TestConfig.class` into the `deleteOrder()` test method: } ---- +[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 + } + } +---- + Due to the robustness of the `ParameterResolver` support in JUnit Jupiter, you can also have multiple dependencies injected into a single method, not only from Spring but also from JUnit Jupiter itself or other third-party extensions. @@ -4668,8 +6264,8 @@ from JUnit Jupiter itself or other third-party extensions. The following example shows how to have both Spring and JUnit Jupiter inject dependencies into the `placeOrderRepeatedly()` test method simultaneously. -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitConfig(TestConfig.class) class OrderServiceIntegrationTests { @@ -4684,6 +6280,21 @@ into the `placeOrderRepeatedly()` test method simultaneously. } ---- +[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 + } + } +---- + Note that the use of `@RepeatedTest` from JUnit Jupiter lets the test method gain access to the `RepetitionInfo`. @@ -4770,7 +6381,8 @@ most part, everything should work as it does at runtime with a few notable excep explained in <>. The following JUnit Jupiter-based example uses Spring MVC Test: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -4778,7 +6390,7 @@ Jupiter-based example uses Spring MVC Test: @SpringJUnitWebConfig(locations = "test-servlet-context.xml") class ExampleTests { - private MockMvc mockMvc; + MockMvc mockMvc; @BeforeEach void setup(WebApplicationContext wac) { @@ -4788,13 +6400,42 @@ Jupiter-based example uses Spring MVC Test: @Test void getAccount() throws Exception { this.mockMvc.perform(get("/accounts/1") - .accept(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.name").value("Lee")); } } ---- +[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 <> is available in Kotlin 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 @@ -4830,8 +6471,8 @@ Spring MVC configuration through the TestContext framework, which loads the Spri configuration and injects a `WebApplicationContext` into the test to use to build a `MockMvc` instance. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitWebConfig(locations = "my-servlet-context.xml") class MyWebTests { @@ -4840,7 +6481,25 @@ configuration and injects a `WebApplicationContext` into the test to use to buil @BeforeEach void setup(WebApplicationContext wac) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); + 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() } // ... @@ -4853,8 +6512,8 @@ configuration. Instead, basic default configuration, roughly comparable to that MVC JavaConfig or the MVC namespace, is automatically created. You can customize it to a degree. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- class MyWebTests { @@ -4870,6 +6529,23 @@ degree. The following example shows how to do so: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyWebTests { + + lateinit var mockMvc : MockMvc + + @BeforeEach + fun setup() { + mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build() + } + + // ... + + } +---- + Which setup option should you use? The `webAppContextSetup` loads your actual Spring MVC configuration, resulting in a more @@ -4879,8 +6555,7 @@ test suite. Furthermore, you can inject mock services into controllers through S configuration to remain focused on testing the web layer. The following example declares a mock service with Mockito: -[source,xml,indent=0] -[subs="verbatim,quotes"] +[source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4890,8 +6565,8 @@ a mock service with Mockito: You can then inject the mock service into the test to set up and verify your expectations, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringJUnitWebConfig(locations = "test-servlet-context.xml") class AccountTests { @@ -4910,6 +6585,26 @@ expectations, as the following example shows: } ---- +[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() + } + + // ... + + } +---- 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 @@ -4932,25 +6627,31 @@ some common and very useful features. For example, you can declare an `Accept` h all requests and expect a status of 200 as well as a `Content-Type` header in all responses, as follows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -// static import of MockMvcBuilders.standaloneSetup + // static import of MockMvcBuilders.standaloneSetup -MockMvc mockMvc = standaloneSetup(new MusicController()) + MockMvc mockMvc = standaloneSetup(new MusicController()) .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON)) .alwaysExpect(status().isOk()) .alwaysExpect(content().contentType("application/json;charset=UTF-8")) .build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + In addition, third-party frameworks (and applications) can pre-package setup 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- // static import of SharedHttpSessionConfigurer.sharedHttpSession @@ -4961,6 +6662,12 @@ You can use it as follows: // Use mockMvc to perform requests... ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + 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. @@ -4970,38 +6677,72 @@ for a list of all MockMvc builder features or use the IDE to explore the availab You can perform requests that use any HTTP method, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); ---- +[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 + } +---- + You can also perform file upload requests that internally use `MockMultipartHttpServletRequest` so that there is no actual parsing of a multipart request. Rather, you have to set it up to be similar to the following example: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8"))); ---- +[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"))) + } +---- + You can specify query parameters in URI template style, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(get("/hotels?thing={thing}", "somewhere")); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + mockMvc.get("/hotels?thing={thing}", "somewhere") +---- You can also add Servlet request parameters that represent either query or form parameters, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(get("/hotels").param("thing", "somewhere")); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.get + + mockMvc.get("/hotels") { + param("thing", "somewhere") + } +---- 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. @@ -5014,22 +6755,32 @@ request URI. If you must test with the full request URI, be sure to set the `con and `servletPath` accordingly so that request mappings work, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) ---- +[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" + } +---- 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: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- class MyWebTests { - private MockMvc mockMvc; + MockMvc mockMvc; @BeforeEach void setup() { @@ -5038,6 +6789,12 @@ properties, as the following example shows: .contextPath("/app").servletPath("/main") .accept(MediaType.APPLICATION_JSON)).build(); } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ---- The preceding properties affect every request performed through the `MockMvc` instance. @@ -5051,12 +6808,22 @@ they must be specified on every request. You can define expectations by appending one or more `.andExpect(..)` calls after performing a request, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.get + + mockMvc.get("/accounts/1").andExpect { + status().isOk() + } +---- + `MockMvcResultMatchers.*` provides a number of expectations, some of which are further nested with more detailed expectations. @@ -5072,20 +6839,33 @@ inspect Servlet specific aspects, such as request and session attributes. The following test asserts that binding or validation failed: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(post("/persons")) .andExpect(status().isOk()) .andExpect(model().attributeHasErrors("person")); ---- +[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") + } + } +---- + 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 `MockMvcResultHandlers`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(post("/persons")) .andDo(print()) @@ -5093,6 +6873,21 @@ request. You can do so as follows, where `print()` is a static import from .andExpect(model().attributeHasErrors("person")); ---- +[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") + } + } +---- + As long as request processing does not cause an unhandled exception, the `print()` method prints all the available result data to `System.out`. Spring Framework 4.2 introduced a `log()` method and two additional variants of the `print()` method, one that accepts an @@ -5106,24 +6901,36 @@ In some cases, you may want to get direct access to the result and verify someth cannot be verified otherwise. This can be achieved by appending `.andReturn()` after all other expectations, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn(); // ... ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + var mvcResult = mockMvc.post("/persons").andExpect { status().isOk() }.andReturn() + // ... +---- + If all tests repeat the same expectations, you can set up common expectations once when building the `MockMvc` instance, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- standaloneSetup(new SimpleController()) .alwaysExpect(status().isOk()) .alwaysExpect(content().contentType("application/json;charset=UTF-8")) .build() ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- Note that common expectations are always applied and cannot be overridden without creating a separate `MockMvc` instance. @@ -5132,25 +6939,49 @@ When a JSON response content contains hypermedia links created with https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the resulting links by using JsonPath expressions, as the following example shows: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people")); ---- +[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") + } + } +---- + When XML response content contains hypermedia links created with https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the resulting links by using XPath expressions: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- Map ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom"); mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML)) .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people")); ---- +[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") + } + } +---- [[spring-mvc-test-async-requests]] ===== Async Requests @@ -5165,21 +6996,21 @@ first, then manually performing the async dispatch, and finally verifying the re Below is an example test for controller methods that return `DeferredResult`, `Callable`, or reactive type such as Reactor `Mono`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- -@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(); + @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")); -} + this.mockMvc.perform(asyncDispatch(mvcResult)) <4> + .andExpect(status().isOk()) <5> + .andExpect(content().string("body")); + } ---- <1> Check response status is still unchanged <2> Async processing must have started @@ -5187,6 +7018,31 @@ void test() throws Exception { <4> Manually perform an ASYNC dispatch (as there is no running container) <5> Verify the final response +[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("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 [[spring-mvc-test-vs-streaming-response]] @@ -5208,13 +7064,16 @@ project Reactor that allows declaring expectations on a stream of data. When setting up a `MockMvc` instance, you can register one or more Servlet `Filter` instances, as the following example shows: -==== -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build(); ---- -==== +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- Registered filters are invoked through the `MockFilterChain` from `spring-test`, and the last filter delegates to the `DispatcherServlet`. @@ -5307,7 +7166,8 @@ supports paging through all messages. How would you go about testing it? With Spring MVC Test, we can easily test if we are able to create a `Message`, as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- MockHttpServletRequestBuilder createMessage = post("/messages/") .param("summary", "Spring Rocks") @@ -5317,6 +7177,20 @@ With Spring MVC Test, we can easily test if we are able to create a `Message`, a .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/messages/123")); ---- +[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") + } + } +---- What if we want to test the form view that lets us create the message? For example, assume our form looks like the following snippet: @@ -5341,20 +7215,31 @@ assume our form looks like the following snippet: How do we ensure that our form produce the correct request to create a new message? A naive attempt might resemble the following: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- mockMvc.perform(get("/messages/form")) .andExpect(xpath("//input[@name='summary']").exists()) .andExpect(xpath("//textarea[@name='text']").exists()); ---- +[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() } + } +---- + This test has some obvious drawbacks. If we update our controller to use the parameter `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: [[spring-mvc-test-server-htmlunit-mock-mvc-test]] -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- String summaryParamName = "summary"; String textParamName = "text"; @@ -5371,6 +7256,24 @@ follows: .andExpect(redirectedUrl("/messages/123")); ---- +[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") + } +---- + This would reduce the risk of our test incorrectly passing, but there are still some problems: @@ -5456,7 +7359,8 @@ First, make sure that you have included a test dependency on We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the `MockMvcWebClientBuilder`, as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- WebClient webClient; @@ -5468,6 +7372,19 @@ We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by usi } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var webClient: WebClient + + @BeforeEach + fun setup(context: WebApplicationContext) { + webClient = MockMvcWebClientBuilder + .webAppContextSetup(context) + .build() + } +---- + NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage, see <>. @@ -5483,18 +7400,26 @@ Now we can use HtmlUnit 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: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val createMsgFormPage = webClient.getPage("http://localhost/messages/form") +---- + NOTE: The default context path is `""`. Alternatively, we can specify the context path, as described in <>. 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: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm"); HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary"); @@ -5504,11 +7429,23 @@ to create a message, as the following example shows: HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit"); HtmlPage newMessagePage = submit.click(); ---- +[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() +---- Finally, we can verify that a new message was created successfully. The following assertions use the https://joel-costigliola.github.io/assertj/[AssertJ] library: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123"); String id = newMessagePage.getHtmlElementById("id").getTextContent(); @@ -5518,6 +7455,17 @@ assertions use the https://joel-costigliola.github.io/assertj/[AssertJ] library: String text = newMessagePage.getHtmlElementById("text").getTextContent(); assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!"); ---- +[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!") +---- The preceding code improves on our <> in a number of ways. @@ -5539,7 +7487,8 @@ In the examples so far, we have used `MockMvcWebClientBuilder` in the simplest w 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: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- WebClient webClient; @@ -5551,9 +7500,23 @@ the Spring TestContext Framework. This approach is repeated in the following exa } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var webClient: WebClient + + @BeforeEach + fun setup(context: WebApplicationContext) { + webClient = MockMvcWebClientBuilder + .webAppContextSetup(context) + .build() + } +---- + We can also specify additional configuration options, as the following example shows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- WebClient webClient; @@ -5570,11 +7533,30 @@ We can also specify additional configuration options, as the following example s .build(); } ---- +[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() + } +---- As an alternative, we can perform the exact same setup by configuring the `MockMvc` instance separately and supplying it to the `MockMvcWebClientBuilder`, as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- MockMvc mockMvc = MockMvcBuilders .webAppContextSetup(context) @@ -5591,6 +7573,12 @@ instance separately and supplying it to the `MockMvcWebClientBuilder`, as follow .build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + This is more verbose, but, by building the `WebClient` with a `MockMvc` instance, we have the full power of MockMvc at our fingertips. @@ -5625,17 +7613,25 @@ afterwards. If one of the fields were named "`summary`", we might have something that resembles the following repeated in multiple places within our tests: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); summaryInput.setValueAttribute(summary); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val summaryInput = currentPage.getHtmlElementById("summary") + summaryInput.setValueAttribute(summary) +---- So what happens if we change the `id` to `smmry`? Doing so would force us to update all 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: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) { setSummary(currentPage, summary); @@ -5648,12 +7644,27 @@ ideally extract this code into its own method, as follows: } ---- +[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) + } +---- + Doing so ensures that we do not have to update all of our tests if we change the UI. 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: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class CreateMessagePage { @@ -5687,6 +7698,34 @@ represents the `HtmlPage` we are currently on, as the following example shows: } } ---- +[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 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() + } + } +} +---- Formerly, this pattern was known as the https://github.com/SeleniumHQ/selenium/wiki/PageObjects[Page Object Pattern]. While we @@ -5702,7 +7741,8 @@ includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver` We can easily create a Selenium WebDriver that integrates with MockMvc by using the `MockMvcHtmlUnitDriverBuilder` as the following example shows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- WebDriver driver; @@ -5711,7 +7751,20 @@ We can easily create a Selenium WebDriver that integrates with MockMvc by using driver = MockMvcHtmlUnitDriverBuilder .webAppContextSetup(context) .build(); -} + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="seconday"] +.Kotlin +---- + lateinit var driver: WebDriver + + @BeforeEach + fun setup(context: WebApplicationContext) { + driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build() + } ---- NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced @@ -5729,26 +7782,42 @@ Now we can use WebDriver 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: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- CreateMessagePage page = CreateMessagePage.to(driver); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val page = CreateMessagePage.to(driver) +---- + We can then fill out the form and submit it to create a message, as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- ViewMessagePage viewMessagePage = page.createMessage(ViewMessagePage.class, expectedSummary, expectedText); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val viewMessagePage = + page.createMessage(ViewMessagePage::class, expectedSummary, expectedText) +---- + This improves on the design of our <> by leveraging the Page Object Pattern. As we mentioned in <>, we can use the Page Object Pattern with HtmlUnit, but it is much easier with WebDriver. Consider the following `CreateMessagePage` implementation: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public class CreateMessagePage extends AbstractPage { // <1> @@ -5778,12 +7847,10 @@ with HtmlUnit, but it is much easier with WebDriver. Consider the following } } ---- - <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 @@ -5792,7 +7859,50 @@ 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)`] 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. +<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 + + fun createMessage(resultPage: Class, 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)`] +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. <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` @@ -5802,16 +7912,24 @@ annotation to look up our submit button with a `css` selector (*input[type=submi Finally, we can verify that a new message was created successfully. The following assertions use the https://joel-costigliola.github.io/assertj/[AssertJ] assertion library: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage); assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message"); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + assertThat(viewMessagePage.message.isEqualTo(expectedMessage) + assertThat(viewMessagePage.success.isEqualTo("Successfully created a new message") +---- 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: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public Message getMessage() throws ParseException { Message message = new Message(); @@ -5822,13 +7940,19 @@ example, it exposes a method that returns a `Message` object: return message; } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun getMessage() = Message(getId(), getCreated(), getSummary(), getText()) +---- We can then use the rich domain objects in our assertions. Lastly, we must not forget to close the `WebDriver` instance when the test is complete, as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @AfterEach void destroy() { @@ -5838,6 +7962,17 @@ as follows: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @AfterEach + fun destroy() { + if (driver != null) { + driver.close() + } + } +---- + For additional information on using WebDriver, see the Selenium https://github.com/SeleniumHQ/selenium/wiki/Getting-Started[WebDriver documentation]. @@ -5848,7 +7983,8 @@ In the examples so far, we have used `MockMvcHtmlUnitDriverBuilder` in the simpl possible, by building a `WebDriver` based on the `WebApplicationContext` loaded for us by the Spring TestContext Framework. This approach is repeated here, as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- WebDriver driver; @@ -5860,9 +7996,23 @@ the Spring TestContext Framework. This approach is repeated here, as follows: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var driver: WebDriver + + @BeforeEach + fun setup(context: WebApplicationContext) { + driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build() + } +---- + We can also specify additional configuration options, as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- WebDriver driver; @@ -5879,11 +8029,30 @@ We can also specify additional configuration options, as follows: .build(); } ---- +[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() + } +---- As an alternative, we can perform the exact same setup by configuring the `MockMvc` instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder`, as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- MockMvc mockMvc = MockMvcBuilders .webAppContextSetup(context) @@ -5900,6 +8069,12 @@ instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder`, as f .build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + This is more verbose, but, by building the `WebDriver` with a `MockMvc` instance, we have the full power of MockMvc at our fingertips. @@ -6041,8 +8216,8 @@ idea is to declare expected requests and to provide "`stub`" responses so that y focus on testing the code in isolation (that is, without running a server). The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- RestTemplate restTemplate = new RestTemplate(); @@ -6053,6 +8228,18 @@ example shows how to do so: mockServer.verify(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val restTemplate = RestTemplate() + + val mockServer = MockRestServiceServer.bindTo(restTemplate).build() + mockServer.expect(requestTo("/greeting")).andRespond(withSuccess()) + + // Test code that uses the above RestTemplate ... + + mockServer.verify() +---- In the preceding example, `MockRestServiceServer` (the central class for client-side REST tests) configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that @@ -6068,19 +8255,24 @@ can set the `ignoreExpectOrder` option when building the server, in which case a expectations are checked (in order) to find a match for a given request. That means requests are allowed to come in any order. The following example uses `ignoreExpectOrder`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build() +---- Even with unordered requests by default, each request is allowed to execute once only. The `expect` method provides an overloaded variant that accepts an `ExpectedCount` argument that specifies a count range (for example, `once`, `manyTimes`, `max`, `min`, `between`, and so on). The following example uses `times`: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- RestTemplate restTemplate = new RestTemplate(); @@ -6092,6 +8284,19 @@ argument that specifies a count range (for example, `once`, `manyTimes`, `max`, mockServer.verify(); ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val restTemplate = RestTemplate() + + val mockServer = MockRestServiceServer.bindTo(restTemplate).build() + mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess()) + mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess()) + + // ... + + mockServer.verify() +---- Note that, when `ignoreExpectOrder` is not set (the default), and, therefore, requests are expected in order of declaration, then that order applies only to the first of any @@ -6105,14 +8310,22 @@ As an alternative to all of the above, the client-side test support also provide bind it to a `MockMvc` instance. That allows processing requests using actual server-side logic but without running a server. The following example shows how to do so: -[source,java,indent=0] -[subs="verbatim,quotes"] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc)); // Test code that uses the above RestTemplate ... ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build() + restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc)) + + // Test code that uses the above RestTemplate ... +---- [[spring-mvc-test-client-static-imports]] ===== Static Imports