diff --git a/src/docs/antora/modules/ROOT/pages/appendix.adoc b/src/docs/antora/modules/ROOT/pages/appendix.adoc index f91ef943..a492bba5 100644 --- a/src/docs/antora/modules/ROOT/pages/appendix.adoc +++ b/src/docs/antora/modules/ROOT/pages/appendix.adoc @@ -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] diff --git a/src/docs/antora/modules/ROOT/pages/testing.adoc b/src/docs/antora/modules/ROOT/pages/testing.adoc index 0e357e7f..d5abaf5e 100644 --- a/src/docs/antora/modules/ROOT/pages/testing.adoc +++ b/src/docs/antora/modules/ROOT/pages/testing.adoc @@ -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`: @@ -16,7 +17,7 @@ package example.order; @ApplicationModuleTest class OrderIntegrationTests { - // Individual test cases go here + // Individual test cases go here } ---- Kotlin:: @@ -28,7 +29,7 @@ package example.order @ApplicationModuleTest class OrderIntegrationTests { - // Individual test cases go here + // Individual test cases go here } ---- ====== @@ -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) @@ -90,7 +91,7 @@ Java:: @ApplicationModuleTest class InventoryIntegrationTests { - @MockitoBean SomeOtherComponent someOtherComponent; + @MockitoBean SomeOtherComponent someOtherComponent; } ---- Kotlin:: @@ -100,7 +101,7 @@ Kotlin:: @ApplicationModuleTest class InventoryIntegrationTests { - @MockitoBean SomeOtherComponent someOtherComponent + @MockitoBean SomeOtherComponent someOtherComponent } ---- ====== @@ -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:: @@ -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 + } } ---- ====== @@ -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 -> … } ---- ====== @@ -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 -> … } ---- ====== @@ -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 -> … } ---- ====== @@ -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 getDefaultCustomizer(Method method, ApplicationContext context) { - return conditionFactory -> …; - } - } + @Override + Function getDefaultCustomizer(Method method, ApplicationContext context) { + return conditionFactory -> …; + } + } } ---- Kotlin:: @@ -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 { - return UnaryOperator { conditionFactory -> … } - } - } + override fun getDefaultCustomizer(method: Method, context: ApplicationContext): UnaryOperator { + 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] +---- + + org.springframework.modulith + spring-modulith-junit + test + +---- + +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]. +