Skip to content

Commit

Permalink
GH-31 - Reference documentation for test optimizations.
Browse files Browse the repository at this point in the history
  • Loading branch information
odrotbohm committed Sep 16, 2024
1 parent d041c6d commit 1092184
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 51 deletions.
13 changes: 13 additions & 0 deletions src/docs/antora/modules/ROOT/pages/appendix.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ Can either be the class name of a custom implementation of `ApplicationModuleDet
|`false`
|Whether to republish outstanding event publications on restarts of the application.
Usually not recommended in multi-instance deployments as other instances might still be processing events.

|`spring.modulith.test.file-modification-detector`
|none
|This can either be one of the predefined values `uncommitted-changes`, `reference-commit`, `default` or the fully-qualified class name of a `FileModificationDetector` that will be used to inspect which files of the projects have been changed.
As the name suggests, `uncommitted-changes` will only consider changed files not already committed.
`reference-commit` will consider all files changed since a given Git commit provided via `spring.modulith.test.reference-commit`, particularly useful CI environments in which that property could point to the commit hash of the last successful build.
`default` detects all uncomitted changes and ones that have not been pushed to the current branch's tracking branch which is primarily useful for local development.

|`spring.modulith.test.reference-commit`
|none
|The commit hash of to which to calculate the set of changed files.
Usually propagated in CI environments to consider all changes since the last successful build.

|===

[appendix]
Expand Down
131 changes: 80 additions & 51 deletions src/docs/antora/modules/ROOT/pages/testing.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[[testing]]
= Integration Testing Application Modules
:tabsize: 2

Spring Modulith allows to run integration tests bootstrapping individual application modules in isolation or combination with others.
To achieve this, place a JUnit test class in an application module package or any sub-package of that and annotate it with `@ApplicationModuleTest`:
Expand All @@ -16,7 +17,7 @@ package example.order;
@ApplicationModuleTest
class OrderIntegrationTests {
// Individual test cases go here
// Individual test cases go here
}
----
Kotlin::
Expand All @@ -28,7 +29,7 @@ package example.order
@ApplicationModuleTest
class OrderIntegrationTests {
// Individual test cases go here
// Individual test cases go here
}
----
======
Expand All @@ -39,11 +40,11 @@ If you configure the log level for `org.springframework.modulith` to `DEBUG`, yo
.The log output of an application module integration test bootstrap
[source, text, subs="macros"]
----
. ____ _ __ _ _
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.0-SNAPSHOT)
Expand Down Expand Up @@ -90,7 +91,7 @@ Java::
@ApplicationModuleTest
class InventoryIntegrationTests {
@MockitoBean SomeOtherComponent someOtherComponent;
@MockitoBean SomeOtherComponent someOtherComponent;
}
----
Kotlin::
Expand All @@ -100,7 +101,7 @@ Kotlin::
@ApplicationModuleTest
class InventoryIntegrationTests {
@MockitoBean SomeOtherComponent someOtherComponent
@MockitoBean SomeOtherComponent someOtherComponent
}
----
======
Expand Down Expand Up @@ -128,10 +129,10 @@ Java::
@ApplicationModuleTest
class SomeApplicationModuleTest {
@Test
public void someModuleIntegrationTest(Scenario scenario) {
// Use the Scenario API to define your integration test
}
@Test
public void someModuleIntegrationTest(Scenario scenario) {
// Use the Scenario API to define your integration test
}
}
----
Kotlin::
Expand All @@ -141,10 +142,10 @@ Kotlin::
@ApplicationModuleTest
class SomeApplicationModuleTest {
@Test
fun someModuleIntegrationTest(scenario: Scenario) {
// Use the Scenario API to define your integration test
}
@Test
fun someModuleIntegrationTest(scenario: Scenario) {
// Use the Scenario API to define your integration test
}
}
----
======
Expand Down Expand Up @@ -254,18 +255,18 @@ Java::
[source, java, role="primary"]
----
scenario.publish(new MyApplicationEvent(…))
.andWaitForEventOfType(SomeOtherEvent.class)
.matching(event -> …)
.toArriveAndVerify(event -> …);
.andWaitForEventOfType(SomeOtherEvent.class)
.matching(event -> …)
.toArriveAndVerify(event -> …);
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
scenario.publish(new MyApplicationEvent(…))
.andWaitForEventOfType(SomeOtherEvent::class.java)
.matching { event -> … }
.toArriveAndVerify { event -> … }
.andWaitForEventOfType(SomeOtherEvent::class.java)
.matching { event -> … }
.toArriveAndVerify { event -> … }
----
======

Expand All @@ -280,16 +281,16 @@ Java::
[source, java, role="primary"]
----
scenario.publish(new MyApplicationEvent(…))
.andWaitForStateChange(() -> someBean.someMethod(…)))
.andVerify(result -> …);
.andWaitForStateChange(() -> someBean.someMethod(…)))
.andVerify(result -> …);
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
scenario.publish(MyApplicationEvent(…))
.andWaitForStateChange { someBean.someMethod(…) }
.andVerify { result -> … }
.andWaitForStateChange { someBean.someMethod(…) }
.andVerify { result -> … }
----
======

Expand All @@ -310,20 +311,20 @@ Java::
[source, java, subs="+quotes", role="primary"]
----
scenario.publish(new MyApplicationEvent(…))
**.customize(conditionFactory -> conditionFactory.atMost(Duration.ofSeconds(2)))**
.andWaitForEventOfType(SomeOtherEvent.class)
.matching(event -> …)
.toArriveAndVerify(event -> …);
**.customize(conditionFactory -> conditionFactory.atMost(Duration.ofSeconds(2)))**
.andWaitForEventOfType(SomeOtherEvent.class)
.matching(event -> …)
.toArriveAndVerify(event -> …);
----
Kotlin::
+
[source, kotlin, subs="+quotes", role="secondary"]
----
scenario.publish(MyApplicationEvent(…))
**.customize { it.atMost(Duration.ofSeconds(2)) }**
.andWaitForEventOfType(SomeOtherEvent::class.java)
.matching { event -> … }
.toArriveAndVerify { event -> … }
**.customize { it.atMost(Duration.ofSeconds(2)) }**
.andWaitForEventOfType(SomeOtherEvent::class.java)
.matching { event -> … }
.toArriveAndVerify { event -> … }
----
======

Expand All @@ -339,18 +340,18 @@ Java::
@ExtendWith(MyCustomizer.class)
class MyTests {
@Test
void myTestCase(Scenario scenario) {
// scenario will be pre-customized with logic defined in MyCustomizer
}
@Test
void myTestCase(Scenario scenario) {
// scenario will be pre-customized with logic defined in MyCustomizer
}
static class MyCustomizer implements ScenarioCustomizer {
static class MyCustomizer implements ScenarioCustomizer {
@Override
Function<ConditionFactory, ConditionFactory> getDefaultCustomizer(Method method, ApplicationContext context) {
return conditionFactory -> …;
}
}
@Override
Function<ConditionFactory, ConditionFactory> getDefaultCustomizer(Method method, ApplicationContext context) {
return conditionFactory -> …;
}
}
}
----
Kotlin::
Expand All @@ -360,17 +361,45 @@ Kotlin::
@ExtendWith(MyCustomizer::class)
class MyTests {
@Test
fun myTestCase(scenario: Scenario) {
// scenario will be pre-customized with logic defined in MyCustomizer
}
@Test
fun myTestCase(scenario: Scenario) {
// scenario will be pre-customized with logic defined in MyCustomizer
}
class MyCustomizer : ScenarioCustomizer {
class MyCustomizer : ScenarioCustomizer {
override fun getDefaultCustomizer(method: Method, context: ApplicationContext): UnaryOperator<ConditionFactory> {
return UnaryOperator { conditionFactory -> … }
}
}
override fun getDefaultCustomizer(method: Method, context: ApplicationContext): UnaryOperator<ConditionFactory> {
return UnaryOperator { conditionFactory -> … }
}
}
}
----
======

[[change-aware-test-execution]]
== Change-Aware Test Execution

As of version 1.3, Spring Modulith ships with a JUnit Jupiter extension that will optimize the execution of tests, so that tests not affected by changes to the project will be skipped.
To enable that optimization, declare the `spring-modulith-junit` artifact as a dependency in test scope:

[source, xml]
----
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-junit</artifactId>
<scope>test</scope>
</dependency>
----

Tests will be selected for execution if they reside in either a root module, a module that has seen a change or one that transitively depends on one that has seen a change.
The optimization will back off optimizing the execution under the following circumstances:

* The test execution originates from an IDE as we assume the execution is triggered explicitly.
* The set of changes contains a change to a resource related to a build system (`pom.xml`, `build.gradle`, `gradle.properties`).
* The set of changes contains a change to any classpath resource.
* The project does not contain a change at all (typical in a CI build).

To optimize the execution in a CI environment, you need to populate the xref:appendix.adoc#configuration-properties[`spring.modulith.test.reference-commit` property] pointing to the commit of the last successful build and make sure that the build checks out all commits up to the reference one.
The algorithm detecting changes to application modules will then consider all files changed in that delta.
To override the project modification detection, declare an implementation of `FileModificationDetector` via the xref:appendix.adoc#configuration-properties[`spring.modulith.test.file-modification-detector` property].

0 comments on commit 1092184

Please sign in to comment.