├── .editorconfig ├── .gitignore ├── .idea └── vcs.xml ├── .mvn ├── extensions.xml ├── jvm.config ├── maven.config ├── surefire-cached.json └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── demo ├── demo-gradle-junit-platform-jupiter-boot32 │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── pom.xml │ ├── settings.gradle │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ └── demo │ │ │ └── DemoGradleJunitPlatformJupiterBoot32Application.java │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── GradleSmartDirtiesJupiterEngineTest.java │ │ │ └── demo │ │ │ ├── AbstractIntegrationTest.java │ │ │ ├── Integration1Test.java │ │ │ ├── Integration2Test.java │ │ │ ├── NoBaseClass1IntegrationTest.java │ │ │ ├── NoBaseClass2IntegrationTest.java │ │ │ ├── SampleBean.java │ │ │ ├── SampleIntegrationTest.java │ │ │ └── Unit1Test.java │ │ └── resources │ │ └── logback-test.xml ├── demo-maven-junit-platform-junit4-boot24 │ ├── pom.xml │ └── src │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── MavenSmartDirtiesVintageEngineTest.java │ │ │ └── demo │ │ │ ├── AbstractIntegrationTest.java │ │ │ ├── Integration1Test.java │ │ │ ├── Integration2Test.java │ │ │ ├── SampleIntegrationTest.java │ │ │ └── Unit1Test.java │ │ └── resources │ │ └── logback-test.xml ├── demo-maven-junit-platform-jupiter-boot35 │ ├── pom.xml │ └── src │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── MavenSmartDirtiesJupiterEngineTest.java │ │ │ ├── SmartDirtiesTestsSupportTest.java │ │ │ └── demo │ │ │ ├── AbstractIntegrationTest.java │ │ │ ├── ExtendWithTest.java │ │ │ ├── Integration1MockBeanTest.java │ │ │ ├── Integration1Test.java │ │ │ ├── Integration2SpringJUnitConfigTest.java │ │ │ ├── Integration2Test.java │ │ │ ├── NoBaseClass1IntegrationTest.java │ │ │ ├── NoBaseClass2IntegrationTest.java │ │ │ ├── SampleBean.java │ │ │ ├── SampleDirtiesContextAfterClassTest.java │ │ │ ├── SampleDirtiesContextBeforeClassTest.java │ │ │ ├── SampleIntegrationTest.java │ │ │ ├── SampleService.java │ │ │ ├── SmartDirtiesJupiterTestsSorterTest.java │ │ │ ├── Unit1Test.java │ │ │ └── WebIntegrationTest.java │ │ └── resources │ │ └── logback-test.xml ├── demo-maven-junit-platform-jupiter-spring5 │ ├── pom.xml │ └── src │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── MavenSmartDirtiesJupiterEngineTest.java │ │ │ ├── SmartDirtiesTestsSupportTest.java │ │ │ └── demo │ │ │ ├── AbstractIntegrationTest.java │ │ │ ├── Integration1Test.java │ │ │ ├── Integration2Test.java │ │ │ ├── SampleBean.java │ │ │ ├── SampleDirtiesContextAfterClassTest.java │ │ │ ├── SampleDirtiesContextBeforeClassTest.java │ │ │ ├── SampleIntegrationTest.java │ │ │ ├── SmartDirtiesJupiterTestsSorterTest.java │ │ │ └── Unit1Test.java │ │ └── resources │ │ ├── junit-platform.properties │ │ └── logback-test.xml ├── demo-maven-junit-platform-jupiter-spring7 │ ├── pom.xml │ └── src │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── MavenSmartDirtiesJupiterEngineTest.java │ │ │ ├── SmartDirtiesTestsSupportTest.java │ │ │ └── demo │ │ │ ├── AbstractIntegrationTest.java │ │ │ ├── Integration1Test.java │ │ │ ├── Integration2Test.java │ │ │ ├── SampleBean.java │ │ │ ├── SampleDirtiesContextAfterClassTest.java │ │ │ ├── SampleDirtiesContextBeforeClassTest.java │ │ │ ├── SampleIntegrationTest.java │ │ │ ├── SmartDirtiesJupiterTestsSorterTest.java │ │ │ └── Unit1Test.java │ │ └── resources │ │ └── logback-test.xml ├── demo-maven-junit-platform-mixed-boot34 │ ├── pom.xml │ └── src │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── MavenSmartDirtiesJupiterEngineTest.java │ │ │ ├── MavenSmartDirtiesTestngEngineTest.java │ │ │ ├── MavenSmartDirtiesVintageEngineTest.java │ │ │ └── demo │ │ │ ├── ExtendWithTest.java │ │ │ ├── Integration1Test.java │ │ │ ├── Integration2SpringJUnitConfigTest.java │ │ │ ├── Integration2Test.java │ │ │ ├── NoBaseClass1IntegrationTest.java │ │ │ ├── NoBaseClass2IntegrationTest.java │ │ │ ├── SampleBean.java │ │ │ ├── SampleDirtiesContextAfterClassTest.java │ │ │ ├── SampleDirtiesContextBeforeClassTest.java │ │ │ ├── SampleIntegrationTest.java │ │ │ └── Unit1Test.java │ │ └── resources │ │ └── logback-test.xml ├── demo-maven-junit-platform-testng-boot32 │ ├── pom.xml │ └── src │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── SmartDirtiesTestngEngineTest.java │ │ │ └── demo │ │ │ ├── AbstractIT.java │ │ │ ├── Integration1IT.java │ │ │ ├── Integration1SecondIT.java │ │ │ ├── Integration2IT.java │ │ │ ├── SampleIT.java │ │ │ └── Unit1Test.java │ │ └── resources │ │ └── logback-test.xml ├── demo-maven-testng-boot24 │ ├── pom.xml │ └── src │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── MavenTestngTest.java │ │ │ └── demo │ │ │ ├── Integration1SecondTest.java │ │ │ ├── Integration1Test.java │ │ │ ├── Integration2Test.java │ │ │ └── Unit1Test.java │ │ └── resources │ │ └── logback-test.xml ├── demo-testkit │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── TestSmartDirtiesTestsHolder.java │ │ │ └── testkit │ │ │ ├── TestContextEventTrackerListener.java │ │ │ └── TestEventTracker.java │ │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.github.seregamorph.testsmartcontext.SpringContextEventLoggerListener └── pom.xml ├── doc ├── active-docker-containers.png ├── miro-logo.png ├── reorder-and-smart-dirties-context.png └── sample-test-suite.png ├── mvnw ├── mvnw.cmd ├── pom.xml └── spring-test-smart-context ├── pom.xml └── src ├── main ├── java │ ├── com │ │ └── github │ │ │ └── seregamorph │ │ │ └── testsmartcontext │ │ │ ├── CurrentTestContext.java │ │ │ ├── IntegrationTestFilter.java │ │ │ ├── JUnitPlatformSupport.java │ │ │ ├── SmartDirtiesContextTestExecutionListener.java │ │ │ ├── SmartDirtiesPostDiscoveryFilter.java │ │ │ ├── SmartDirtiesTestsSorter.java │ │ │ ├── SmartDirtiesTestsSupport.java │ │ │ ├── SpringContextEventLoggerListener.java │ │ │ ├── SpringContextEventLoggerListenerCustomizerFactory.java │ │ │ ├── TestClassExtractor.java │ │ │ ├── TestSortResult.java │ │ │ ├── jdbc │ │ │ ├── CloseableDelegatingDataSource.java │ │ │ ├── GuavaSuppliers.java │ │ │ └── LateInitDataSource.java │ │ │ ├── junit4 │ │ │ └── AbstractJUnit4SpringIntegrationTest.java │ │ │ ├── jupiter │ │ │ ├── AbstractJUnitSpringIntegrationTest.java │ │ │ └── SmartDirtiesClassOrderer.java │ │ │ └── testng │ │ │ ├── AbstractTestNGSpringIntegrationTest.java │ │ │ └── SmartDirtiesSuiteListener.java │ └── org │ │ └── springframework │ │ └── test │ │ └── context │ │ └── BootstrapUtilsHelper.java └── resources │ ├── META-INF │ ├── services │ │ ├── org.junit.platform.launcher.PostDiscoveryFilter │ │ └── org.testng.ITestNGListener │ └── spring.factories │ └── junit-platform.properties └── test └── java └── com └── github └── seregamorph └── testsmartcontext └── SpringContextEventLoggerListenerTest.java /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | max_line_length = 120 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | charset = utf-8 8 | indent_style = space 9 | 10 | [{*.sh,gradlew}] 11 | end_of_line = lf 12 | 13 | [{*.bat,*.cmd}] 14 | end_of_line = crlf 15 | 16 | [*.md] 17 | # Trailing space means "paragraph continues" in markdown 18 | trim_trailing_whitespace = false 19 | 20 | [*.java] 21 | indent_size = 4 22 | ij_continuation_indent_size = 4 23 | # Doc: https://youtrack.jetbrains.com/issue/IDEA-170643#focus=streamItem-27-3708697.0-0 24 | # $ means "static" 25 | ij_java_wrap_long_lines = true 26 | ij_java_wrap_comments = true 27 | ij_java_imports_layout = $*,|,* 28 | ij_java_use_single_class_imports = true 29 | # Below does not seem to work as of 2021.1.1 even though 30 | # https://youtrack.jetbrains.com/issue/IDEA-225733 is resolved 31 | wildcard_import_limit = 999 32 | ij_kotlin_name_count_to_use_star_import = 999 33 | ij_kotlin_name_count_to_use_star_import_for_members = 999 34 | ij_java_class_count_to_use_import_on_demand = 999 35 | ij_java_names_count_to_use_import_on_demand = 999 36 | 37 | [*.js] 38 | indent_size = 2 39 | 40 | [*.xml] 41 | indent_size = 4 42 | 43 | [{*.yml,*.yaml}] 44 | indent_size = 2 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .mvn/.develocity 3 | target 4 | build 5 | .gradle 6 | dep*.txt 7 | 8 | # Compiled class file 9 | *.class 10 | 11 | # Log file 12 | *.log 13 | 14 | # BlueJ files 15 | *.ctxt 16 | 17 | # Mobile Tools for Java (J2ME) 18 | .mtj.tmp/ 19 | 20 | # Package Files # 21 | *.jar 22 | !gradle-wrapper.jar 23 | *.war 24 | *.nar 25 | *.ear 26 | *.zip 27 | *.tar.gz 28 | *.rar 29 | 30 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 31 | hs_err_pid* 32 | replay_pid* 33 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.github.seregamorph 6 | surefire-cached-extension 7 | 0.13 8 | 9 | 10 | -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | -Djava.awt.headless=true -Xmx1024m 2 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -T1 2 | --strict-checksums 3 | -e 4 | -------------------------------------------------------------------------------- /.mvn/surefire-cached.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "Configuration for Maven surefire-cached-extension", 3 | "common": { 4 | "inputIgnoredProperties": [ 5 | "java.version", 6 | "os.arch", 7 | "os.name" 8 | ], 9 | "cacheExcludes": [ 10 | ] 11 | }, 12 | "surefire": { 13 | "artifacts": { 14 | "surefire-reports": { 15 | "includes": [ 16 | "surefire-reports/TEST-*.xml" 17 | ] 18 | } 19 | } 20 | }, 21 | "failsafe": { 22 | "artifacts": { 23 | "failsafe-reports": { 24 | "includes": [ 25 | "failsafe-reports/TEST-*.xml" 26 | ] 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregamorph/spring-test-smart-context/7fd2b8420f2aeaf1d6cf1815dc0898981abbb6e1/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /demo/demo-gradle-junit-platform-jupiter-boot32/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.2.2' 4 | id 'io.spring.dependency-management' version '1.1.4' 5 | } 6 | 7 | group = 'com.github.seregamorph' 8 | 9 | java { 10 | sourceCompatibility = '17' 11 | } 12 | 13 | repositories { 14 | mavenLocal() 15 | mavenCentral() 16 | } 17 | 18 | def stscVersion = "0.14-SNAPSHOT" 19 | dependencies { 20 | implementation "org.springframework.boot:spring-boot-starter" 21 | implementation "org.springframework.boot:spring-boot-starter-web" 22 | 23 | testImplementation "com.github.seregamorph:spring-test-smart-context:$stscVersion" 24 | testImplementation "com.github.seregamorph:demo-testkit:$stscVersion" 25 | testImplementation "org.junit.platform:junit-platform-testkit" 26 | testImplementation "org.springframework.boot:spring-boot-starter-test" 27 | } 28 | 29 | tasks.named("test") { 30 | useJUnitPlatform() 31 | } 32 | -------------------------------------------------------------------------------- /demo/demo-gradle-junit-platform-jupiter-boot32/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregamorph/spring-test-smart-context/7fd2b8420f2aeaf1d6cf1815dc0898981abbb6e1/demo/demo-gradle-junit-platform-jupiter-boot32/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /demo/demo-gradle-junit-platform-jupiter-boot32/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /demo/demo-gradle-junit-platform-jupiter-boot32/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /demo/demo-gradle-junit-platform-jupiter-boot32/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-gradle-junit-platform-jupiter-boot32 14 | 15 | Demo project with tests launched via Gradle junit-platform jupiter 16 | 17 | 18 | 19 | true 20 | true 21 | true 22 | 23 | 24 | 25 | 26 | 27 | org.codehaus.mojo 28 | exec-maven-plugin 29 | ${exec-maven-plugin.version} 30 | 31 | 32 | gradle 33 | install 34 | 35 | ./gradlew 36 | 37 | --no-daemon 38 | clean 39 | build 40 | -i 41 | 42 | 43 | 44 | exec 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /demo/demo-gradle-junit-platform-jupiter-boot32/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'demo-gradle-junit-platform-jupiter-boot32' 2 | -------------------------------------------------------------------------------- /demo/demo-gradle-junit-platform-jupiter-boot32/src/main/java/com/github/seregamorph/testsmartcontext/demo/DemoGradleJunitPlatformJupiterBoot32Application.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoGradleJunitPlatformJupiterBoot32Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoGradleJunitPlatformJupiterBoot32Application.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /demo/demo-gradle-junit-platform-jupiter-boot32/src/test/java/com/github/seregamorph/testsmartcontext/GradleSmartDirtiesJupiterEngineTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; 7 | import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; 8 | 9 | import com.github.seregamorph.testsmartcontext.demo.Integration1Test; 10 | import com.github.seregamorph.testsmartcontext.demo.Integration2Test; 11 | import com.github.seregamorph.testsmartcontext.demo.NoBaseClass1IntegrationTest; 12 | import com.github.seregamorph.testsmartcontext.demo.NoBaseClass2IntegrationTest; 13 | import com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest; 14 | import com.github.seregamorph.testsmartcontext.demo.Unit1Test; 15 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | import org.junit.jupiter.api.AfterAll; 20 | import org.junit.jupiter.api.BeforeAll; 21 | import org.junit.jupiter.api.Test; 22 | import org.junit.platform.testkit.engine.EngineTestKit; 23 | 24 | public class GradleSmartDirtiesJupiterEngineTest { 25 | 26 | private static final String ENGINE = "junit-jupiter"; 27 | 28 | private static Map, SmartDirtiesTestsSupport.ClassOrderState>> prevEngineClassOrderStateMap; 29 | 30 | @BeforeAll 31 | public static void beforeClass() { 32 | prevEngineClassOrderStateMap = SmartDirtiesTestsSupport.setEngineClassOrderStateMap(null); 33 | TestEventTracker.startTracking(); 34 | } 35 | 36 | @AfterAll 37 | public static void afterClass() { 38 | TestEventTracker.stopTracking(); 39 | SmartDirtiesTestsSupport.setEngineClassOrderStateMap(prevEngineClassOrderStateMap); 40 | } 41 | 42 | @Test 43 | public void testSuite() { 44 | // to avoid confusion of duplicated test execution output 45 | System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 46 | System.out.println(">>>EngineTestKit duplicating the suite>>>"); 47 | 48 | var events = EngineTestKit.execute(ENGINE, request() 49 | .selectors(selectPackage("com.github.seregamorph.testsmartcontext.demo")) 50 | .build()) 51 | .containerEvents(); 52 | 53 | // 9 = 5 ITs + 2 Nested + 1 UTs + 1 suite 54 | events.assertStatistics(stats -> stats 55 | .started(9) 56 | .succeeded(9) 57 | .finished(9) 58 | .aborted(0) 59 | .failed(0)); 60 | 61 | assertEquals(List.of( 62 | Integration1Test.class, 63 | Integration2Test.class, 64 | NoBaseClass2IntegrationTest.class, 65 | NoBaseClass1IntegrationTest.class, 66 | SampleIntegrationTest.class, 67 | Unit1Test.class 68 | ), new ArrayList<>(SmartDirtiesTestsSupport.getTestClasses(ENGINE))); 69 | 70 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration1Test.class)); 71 | assertFalse(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration1Test.NestedTest.class)); 72 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration2Test.class)); 73 | assertFalse(SmartDirtiesTestsSupport.isFirstClassPerConfig(NoBaseClass1IntegrationTest.class)); 74 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(NoBaseClass2IntegrationTest.class)); 75 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(SampleIntegrationTest.class)); 76 | 77 | assertFalse(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration1Test.NestedTest.class)); 78 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration1Test.class)); 79 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration2Test.class)); 80 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(NoBaseClass1IntegrationTest.class)); 81 | assertFalse(SmartDirtiesTestsSupport.isLastClassPerConfig(NoBaseClass2IntegrationTest.class)); 82 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(SampleIntegrationTest.class)); 83 | 84 | TestEventTracker.assertConsumedEvent("Running Unit1Test.test"); 85 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 86 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 87 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 88 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 89 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 90 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 91 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.NoBaseClass2IntegrationTest"); 92 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.NoBaseClass2IntegrationTest"); 93 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.NoBaseClass1IntegrationTest"); 94 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 95 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 96 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 97 | TestEventTracker.assertEmpty(); 98 | 99 | System.out.println("<< 2 | 3 | 4 | %d{HH:mm:ss} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-junit4-boot24/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-maven-junit-platform-junit4-boot24 14 | 15 | Demo project with tests launched via Maven junit-platform junit4 vintage engine 16 | 17 | 18 | 2.4.2 19 | true 20 | 21 | 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-surefire-plugin 27 | 28 | 29 | org.apache.maven.surefire 30 | surefire-junit-platform 31 | ${maven-surefire.version} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-dependencies 43 | ${spring-boot.version} 44 | pom 45 | import 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-web 54 | 55 | 56 | 57 | 58 | com.github.seregamorph 59 | spring-test-smart-context 60 | test 61 | 62 | 63 | 64 | com.github.seregamorph 65 | demo-testkit 66 | test 67 | 68 | 69 | org.junit.platform 70 | junit-platform-testkit 71 | test 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-test 76 | test 77 | 78 | 79 | org.junit.jupiter 80 | junit-jupiter 81 | 82 | 83 | 84 | 85 | 95 | org.junit.jupiter 96 | junit-jupiter-api 97 | 5.10.0 98 | 99 | 100 | org.junit.vintage 101 | junit-vintage-engine 102 | test 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-junit4-boot24/src/test/java/com/github/seregamorph/testsmartcontext/MavenSmartDirtiesVintageEngineTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; 6 | import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; 7 | 8 | import com.github.seregamorph.testsmartcontext.demo.Integration1Test; 9 | import com.github.seregamorph.testsmartcontext.demo.Integration2Test; 10 | import com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest; 11 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | import org.junit.AfterClass; 16 | import org.junit.BeforeClass; 17 | import org.junit.Test; 18 | import org.junit.platform.testkit.engine.EngineTestKit; 19 | 20 | public class MavenSmartDirtiesVintageEngineTest { 21 | 22 | private static final String ENGINE = "junit-vintage"; 23 | 24 | private static Map, SmartDirtiesTestsSupport.ClassOrderState>> prevEngineClassOrderStateMap; 25 | 26 | @BeforeClass 27 | public static void beforeClass() { 28 | prevEngineClassOrderStateMap = SmartDirtiesTestsSupport.setEngineClassOrderStateMap(null); 29 | TestEventTracker.startTracking(); 30 | } 31 | 32 | @AfterClass 33 | public static void afterClass() { 34 | TestEventTracker.stopTracking(); 35 | SmartDirtiesTestsSupport.setEngineClassOrderStateMap(prevEngineClassOrderStateMap); 36 | } 37 | 38 | @Test 39 | public void testSuite() { 40 | // to avoid confusion of duplicated test execution output 41 | System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 42 | System.out.println(">>>EngineTestKit duplicating the suite>>>"); 43 | 44 | var events = EngineTestKit.execute(ENGINE, request() 45 | .selectors(selectPackage("com.github.seregamorph.testsmartcontext.demo")) 46 | .filters(new SmartDirtiesPostDiscoveryFilter()) 47 | .build()) 48 | .containerEvents(); 49 | 50 | // 5 = 3 ITs + 1 UTs + 1 suite 51 | events.assertStatistics(stats -> stats 52 | .started(5) 53 | .succeeded(5) 54 | .finished(5) 55 | .aborted(0) 56 | .failed(0)); 57 | 58 | assertEquals(List.of( 59 | Integration1Test.class, 60 | Integration2Test.class, 61 | SampleIntegrationTest.class 62 | ), new ArrayList<>(TestSmartDirtiesTestsHolder.getIntegrationTestClasses(ENGINE))); 63 | 64 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration1Test.class)); 65 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration2Test.class)); 66 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(SampleIntegrationTest.class)); 67 | 68 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration1Test.class)); 69 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration2Test.class)); 70 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(SampleIntegrationTest.class)); 71 | 72 | TestEventTracker.assertConsumedEvent("Running Unit1Test"); 73 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 74 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 75 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 76 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 77 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 78 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 79 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 80 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 81 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 82 | TestEventTracker.assertEmpty(); 83 | 84 | System.out.println("<< 2 | 3 | 4 | %d{HH:mm:ss} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-maven-junit-platform-jupiter-boot35 14 | 15 | Demo project with tests launched via Maven junit-platform jupiter with spring-boot 3.5 16 | 17 | 18 | 3.5.0 19 | true 20 | 21 | 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-surefire-plugin 27 | 28 | 29 | org.apache.maven.surefire 30 | surefire-junit-platform 31 | ${maven-surefire.version} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-dependencies 43 | ${spring-boot.version} 44 | pom 45 | import 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-web 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-actuator 58 | 59 | 60 | 61 | org.junit.platform 62 | junit-platform-testkit 63 | test 64 | 65 | 66 | 67 | 68 | com.github.seregamorph 69 | spring-test-smart-context 70 | test 71 | 72 | 73 | com.github.seregamorph 74 | demo-testkit 75 | test 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-starter-test 81 | test 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/SmartDirtiesTestsSupportTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Nested; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.TestPropertySource; 11 | 12 | class SmartDirtiesTestsSupportTest { 13 | 14 | @BeforeEach 15 | public void prepare() { 16 | SmartDirtiesTestsSupport.setTestClassesLists("SmartDirtiesTestsHolderTest", 17 | TestSortResult.singletonList(TestRootTest.class)); 18 | } 19 | 20 | @Test 21 | public void nestedInheritShouldPass() { 22 | SmartDirtiesTestsSupport.verifyInnerClass(TestRootTest.NestedInheritTest.class); 23 | } 24 | 25 | @Test 26 | public void nestedCustomShouldFail() { 27 | var ise = Assertions.assertThrows(IllegalStateException.class, 28 | () -> SmartDirtiesTestsSupport.verifyInnerClass(TestRootTest.NestedCustomTest.class)); 29 | assertEquals("Nested inner class com.github.seregamorph.testsmartcontext.TestRootTest$NestedCustomTest " + 30 | "declares custom context configuration which differs from enclosing class com.github.seregamorph" + 31 | ".testsmartcontext.TestRootTest. This is not properly supported by the spring-test-smart-context ordering" + 32 | " because of framework limitations. Please extract inner test class to upper level.", ise.getMessage()); 33 | } 34 | } 35 | 36 | @SpringBootTest(classes = { 37 | TestRootTest.Configuration.class 38 | }) 39 | class TestRootTest { 40 | 41 | public static class Configuration { 42 | 43 | } 44 | 45 | @Nested 46 | public class NestedInheritTest { 47 | } 48 | 49 | @Nested 50 | @TestPropertySource(properties = { 51 | "parameter = value" 52 | }) 53 | public class NestedCustomTest { 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import com.github.seregamorph.testsmartcontext.jupiter.AbstractJUnitSpringIntegrationTest; 4 | import org.junit.jupiter.api.BeforeEach; 5 | 6 | public abstract class AbstractIntegrationTest extends AbstractJUnitSpringIntegrationTest { 7 | 8 | @BeforeEach 9 | public void beforeMethod() { 10 | System.out.println("Running " + getClass()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/ExtendWithTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.boot.test.system.OutputCaptureExtension; 7 | 8 | @SpringBootTest(classes = SampleIntegrationTest.Configuration.class) 9 | @ExtendWith(OutputCaptureExtension.class) 10 | public class ExtendWithTest { 11 | 12 | @Test 13 | public void test() { 14 | System.out.println("Running " + getClass().getName() + ".test"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration1MockBeanTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.mockito.Mockito.reset; 5 | import static org.mockito.Mockito.when; 6 | 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.mock.mockito.MockBean; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.web.WebAppConfiguration; 13 | 14 | @WebAppConfiguration 15 | @ContextConfiguration(classes = { 16 | Integration1Test.Configuration.class 17 | }) 18 | public class Integration1MockBeanTest extends AbstractIntegrationTest { 19 | 20 | @Autowired 21 | private SampleService sampleService; 22 | 23 | @SuppressWarnings("removal") 24 | @MockBean 25 | private SampleBean sampleBean; 26 | 27 | @BeforeEach 28 | public void setUp() { 29 | reset(sampleBean); 30 | when(sampleBean.getValue()).thenReturn("mock"); 31 | } 32 | 33 | @Test 34 | public void test() { 35 | assertEquals("mock", sampleBean.getValue()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration1Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Import; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.web.WebAppConfiguration; 11 | 12 | @WebAppConfiguration 13 | @ContextConfiguration(classes = { 14 | Integration1Test.Configuration.class 15 | }) 16 | public class Integration1Test extends AbstractIntegrationTest { 17 | 18 | @Autowired 19 | private SampleBean rootBean; 20 | 21 | @Test 22 | public void test() { 23 | System.out.println("Integration1Test.test " + rootBean); 24 | } 25 | 26 | @Nested 27 | public class NestedTest { 28 | 29 | @Autowired 30 | private SampleBean nestedBean; 31 | 32 | @Test 33 | public void nested() { 34 | System.out.println("Integration1Test.NestedTest.test " + nestedBean); 35 | } 36 | 37 | @Nested 38 | public class DeeplyNestedTest { 39 | 40 | @Autowired 41 | private SampleService sampleService; 42 | 43 | @Test 44 | public void deeplyNested() { 45 | assertEquals("default", sampleService.getValue()); 46 | System.out.println("Integration1Test.NestedTest.DeeplyNestedTest.deeplyNested " + sampleService); 47 | } 48 | } 49 | } 50 | 51 | @Import({ 52 | SampleBean.class, 53 | SampleService.class 54 | }) 55 | public static class Configuration { 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration2SpringJUnitConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; 5 | 6 | @SpringJUnitConfig(classes = { 7 | Integration2Test.Configuration.class 8 | }) 9 | public class Integration2SpringJUnitConfigTest { 10 | 11 | @Test 12 | public void test() { 13 | System.out.println("Integration2SpringJUnitConfigTest.test"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration2Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Nested; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.test.context.ContextConfiguration; 6 | 7 | @ContextConfiguration(classes = { 8 | Integration2Test.Configuration.class 9 | }) 10 | public class Integration2Test extends AbstractIntegrationTest { 11 | 12 | @Test 13 | public void test() { 14 | System.out.println("Integration2Test.test"); 15 | } 16 | 17 | @Nested 18 | public class NestedTest { 19 | 20 | @Test 21 | public void nested() { 22 | System.out.println("Integration2Test.NestedTest.test"); 23 | } 24 | } 25 | 26 | public static class Configuration { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/NoBaseClass1IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest(classes = SampleIntegrationTest.Configuration.class) 7 | public class NoBaseClass1IntegrationTest { 8 | 9 | @Test 10 | public void test() { 11 | System.out.println("Running " + getClass().getName() + ".test"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/NoBaseClass2IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest(classes = SampleIntegrationTest.Configuration.class) 7 | public class NoBaseClass2IntegrationTest { 8 | 9 | @Test 10 | public void test() { 11 | System.out.println("Running " + getClass().getName() + ".test"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleBean.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | public class SampleBean { 4 | 5 | public String getValue() { 6 | return "default"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleDirtiesContextAfterClassTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.test.annotation.DirtiesContext; 6 | 7 | @SpringBootTest(classes = SampleIntegrationTest.Configuration.class) 8 | @DirtiesContext 9 | public class SampleDirtiesContextAfterClassTest { 10 | 11 | @Test 12 | public void test() { 13 | System.out.println("Running " + getClass().getName() + ".test"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleDirtiesContextBeforeClassTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.annotation.DirtiesContext; 7 | 8 | @SpringBootTest(classes = SampleIntegrationTest.Configuration.class) 9 | @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS) 10 | public class SampleDirtiesContextBeforeClassTest { 11 | 12 | @Test 13 | public void test() { 14 | TestEventTracker.trackEvent("Running " + getClass().getSimpleName() + ".test"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 4 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.TestPropertySource; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; 15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 16 | import org.springframework.web.context.WebApplicationContext; 17 | 18 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) 19 | @ContextConfiguration(classes = { 20 | SampleIntegrationTest.Configuration.class 21 | }) 22 | @ActiveProfiles("test") 23 | @TestPropertySource(properties = { 24 | "parameter = value" 25 | }) 26 | public class SampleIntegrationTest extends AbstractIntegrationTest { 27 | 28 | @Autowired 29 | private MockMvc mockMvc; 30 | 31 | @Test 32 | public void test404() throws Exception { 33 | mockMvc.perform(get("/article")) 34 | .andExpect(status().isNotFound()); 35 | } 36 | 37 | public static class Configuration { 38 | 39 | @Bean 40 | public MockMvc mockMvc(WebApplicationContext webApplicationContext) { 41 | DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(webApplicationContext); 42 | return builder.build(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleService.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | public class SampleService { 4 | 5 | private final SampleBean sampleBean; 6 | 7 | public SampleService(SampleBean sampleBean) { 8 | this.sampleBean = sampleBean; 9 | } 10 | 11 | public String getValue() { 12 | return sampleBean.getValue(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/SmartDirtiesJupiterTestsSorterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.github.seregamorph.testsmartcontext.SmartDirtiesTestsSorter; 6 | import com.github.seregamorph.testsmartcontext.TestClassExtractor; 7 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import org.junit.jupiter.api.Test; 11 | 12 | public class SmartDirtiesJupiterTestsSorterTest { 13 | 14 | @Test 15 | public void shouldSortMostlyAlphabeticallyAndGroupSameConfigurations() { 16 | TestEventTracker.trackEvent("Started " + getClass().getSimpleName() + 17 | ".shouldSortMostlyAlphabeticallyAndGroupSameConfigurations"); 18 | // mostly: @DirtiesContext should go last. 19 | SmartDirtiesTestsSorter sorter = SmartDirtiesTestsSorter.getInstance(); 20 | var testItems = Arrays.asList( 21 | Integration2Test.class, 22 | SampleIntegrationTest.class, 23 | Integration1Test.class, 24 | Integration1MockBeanTest.class, 25 | Unit1Test.class, 26 | SampleDirtiesContextAfterClassTest.class, 27 | SampleDirtiesContextBeforeClassTest.class, 28 | NoBaseClass2IntegrationTest.class, 29 | NoBaseClass1IntegrationTest.class, 30 | ExtendWithTest.class, 31 | SmartDirtiesJupiterTestsSorterTest.class 32 | ); 33 | var itClassesLists = sorter.sort(testItems, TestClassExtractor.ofClass(testClass -> testClass)); 34 | 35 | assertEquals(Arrays.asList( 36 | // UT 37 | SmartDirtiesJupiterTestsSorterTest.class, 38 | Unit1Test.class, 39 | // IT 1 40 | SampleDirtiesContextBeforeClassTest.class, 41 | ExtendWithTest.class, 42 | NoBaseClass1IntegrationTest.class, 43 | NoBaseClass2IntegrationTest.class, 44 | SampleDirtiesContextAfterClassTest.class, 45 | // IT 2 46 | Integration1MockBeanTest.class, 47 | // IT 3 48 | Integration1Test.class, 49 | // IT 4 50 | Integration2Test.class, 51 | // IT 5 52 | SampleIntegrationTest.class 53 | ), testItems); 54 | 55 | assertEquals(List.of( 56 | List.of(SampleDirtiesContextBeforeClassTest.class, ExtendWithTest.class, 57 | NoBaseClass1IntegrationTest.class, NoBaseClass2IntegrationTest.class, 58 | SampleDirtiesContextAfterClassTest.class), 59 | List.of(Integration1MockBeanTest.class), 60 | List.of(Integration1Test.class), 61 | List.of(Integration2Test.class), 62 | List.of(SampleIntegrationTest.class) 63 | ), itClassesLists.getSortedConfigToTests()); 64 | 65 | TestEventTracker.trackEvent("Finished " + getClass().getSimpleName() + 66 | ".shouldSortMostlyAlphabeticallyAndGroupSameConfigurations"); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/Unit1Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 6 | import org.junit.jupiter.api.Test; 7 | 8 | public class Unit1Test { 9 | 10 | @Test 11 | public void test() { 12 | TestEventTracker.trackEvent("Running " + getClass().getName() + ".test"); 13 | assertTrue(true); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/java/com/github/seregamorph/testsmartcontext/demo/WebIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.web.client.TestRestTemplate; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.TestPropertySource; 13 | 14 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 15 | @EnableAutoConfiguration 16 | @ContextConfiguration(classes = { 17 | WebIntegrationTest.Configuration.class 18 | }) 19 | @ActiveProfiles("test") 20 | @TestPropertySource(properties = { 21 | "parameter = value" 22 | }) 23 | public class WebIntegrationTest extends AbstractIntegrationTest { 24 | 25 | @Autowired 26 | private TestRestTemplate testRestTemplate; 27 | 28 | @Test 29 | public void test404() { 30 | var entity = testRestTemplate.getForEntity("/article", String.class); 31 | assertEquals(404, entity.getStatusCode().value()); 32 | } 33 | 34 | public static class Configuration { 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-boot35/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-maven-junit-platform-jupiter-spring5 14 | 15 | Demo project with tests launched via Maven junit-platform jupiter on bare spring 5.3 16 | 17 | 18 | true 19 | 20 | 5.3.39 21 | 1.2.3 22 | 1.11.3 23 | 5.11.3 24 | 4.0.1 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-surefire-plugin 32 | 33 | 34 | org.apache.maven.surefire 35 | surefire-junit-platform 36 | ${maven-surefire.version} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework 46 | spring-webmvc 47 | ${spring-framework.version} 48 | 49 | 50 | javax.servlet 51 | javax.servlet-api 52 | ${javax.servlet-api.version} 53 | 54 | 55 | 56 | ch.qos.logback 57 | logback-classic 58 | ${logback.version} 59 | 60 | 61 | org.junit.jupiter 62 | junit-jupiter 63 | ${junit-jupiter.version} 64 | test 65 | 66 | 67 | org.junit.platform 68 | junit-platform-testkit 69 | ${junit-platform.version} 70 | test 71 | 72 | 73 | 74 | 75 | com.github.seregamorph 76 | spring-test-smart-context 77 | test 78 | 79 | 80 | com.github.seregamorph 81 | demo-testkit 82 | test 83 | 84 | 85 | 86 | org.springframework 87 | spring-test 88 | ${spring-framework.version} 89 | test 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/SmartDirtiesTestsSupportTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Nested; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.TestPropertySource; 12 | import org.springframework.test.context.junit.jupiter.SpringExtension; 13 | 14 | class SmartDirtiesTestsSupportTest { 15 | 16 | @BeforeEach 17 | public void prepare() { 18 | SmartDirtiesTestsSupport.setTestClassesLists("SmartDirtiesTestsHolderTest", 19 | TestSortResult.singletonList(TestRootTest.class)); 20 | } 21 | 22 | @Test 23 | public void nestedInheritShouldPass() { 24 | SmartDirtiesTestsSupport.verifyInnerClass(TestRootTest.NestedInheritTest.class); 25 | } 26 | 27 | @Test 28 | public void nestedCustomShouldFail() { 29 | var ise = Assertions.assertThrows(IllegalStateException.class, 30 | () -> SmartDirtiesTestsSupport.verifyInnerClass(TestRootTest.NestedCustomTest.class)); 31 | assertEquals("Nested inner class com.github.seregamorph.testsmartcontext.TestRootTest$NestedCustomTest " + 32 | "declares custom context configuration which differs from enclosing class com.github.seregamorph" + 33 | ".testsmartcontext.TestRootTest. This is not properly supported by the spring-test-smart-context ordering" + 34 | " because of framework limitations. Please extract inner test class to upper level.", ise.getMessage()); 35 | } 36 | } 37 | 38 | @ExtendWith(SpringExtension.class) 39 | @ContextConfiguration(classes = { 40 | TestRootTest.Configuration.class 41 | }) 42 | class TestRootTest { 43 | 44 | public static class Configuration { 45 | 46 | } 47 | 48 | @Nested 49 | public class NestedInheritTest { 50 | } 51 | 52 | @Nested 53 | @TestPropertySource(properties = { 54 | "parameter = value" 55 | }) 56 | public class NestedCustomTest { 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import com.github.seregamorph.testsmartcontext.jupiter.AbstractJUnitSpringIntegrationTest; 4 | import org.junit.jupiter.api.BeforeEach; 5 | 6 | public abstract class AbstractIntegrationTest extends AbstractJUnitSpringIntegrationTest { 7 | 8 | @BeforeEach 9 | public void beforeMethod() { 10 | System.out.println("Running " + getClass()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration1Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Nested; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Import; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.web.WebAppConfiguration; 9 | 10 | @WebAppConfiguration 11 | @ContextConfiguration(classes = { 12 | Integration1Test.Configuration.class 13 | }) 14 | public class Integration1Test extends AbstractIntegrationTest { 15 | 16 | @Autowired 17 | private SampleBean rootBean; 18 | 19 | @Test 20 | public void test() { 21 | System.out.println("Integration1Test.test " + rootBean); 22 | } 23 | 24 | @Nested 25 | public class NestedTest { 26 | 27 | @Autowired 28 | private SampleBean nestedBean; 29 | 30 | @Test 31 | public void nested() { 32 | System.out.println("Integration1Test.NestedTest.test " + nestedBean); 33 | } 34 | 35 | @Nested 36 | public class DeeplyNestedTest { 37 | 38 | @Autowired 39 | private SampleBean deeplyNestedBean; 40 | 41 | @Test 42 | public void deeplyNested() { 43 | System.out.println("Integration1Test.NestedTest.DeeplyNestedTest.deeplyNested " + deeplyNestedBean); 44 | } 45 | } 46 | } 47 | 48 | @Import(SampleBean.class) 49 | public static class Configuration { 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration2Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Nested; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.test.context.ContextConfiguration; 6 | 7 | @ContextConfiguration(classes = { 8 | Integration2Test.Configuration.class 9 | }) 10 | public class Integration2Test extends AbstractIntegrationTest { 11 | 12 | @Test 13 | public void test() { 14 | System.out.println("Integration2Test.test"); 15 | } 16 | 17 | @Nested 18 | public class NestedTest { 19 | 20 | @Test 21 | public void nested() { 22 | System.out.println("Integration2Test.NestedTest.test"); 23 | } 24 | } 25 | 26 | public static class Configuration { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleBean.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | public class SampleBean { 4 | } 5 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleDirtiesContextAfterClassTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.test.annotation.DirtiesContext; 5 | import org.springframework.test.context.ContextConfiguration; 6 | import org.springframework.test.context.web.WebAppConfiguration; 7 | 8 | @WebAppConfiguration 9 | @ContextConfiguration(classes = SampleIntegrationTest.Configuration.class) 10 | @DirtiesContext 11 | public class SampleDirtiesContextAfterClassTest extends AbstractIntegrationTest { 12 | 13 | @Test 14 | public void test() { 15 | System.out.println("Running " + getClass().getName() + ".test"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleDirtiesContextBeforeClassTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.test.annotation.DirtiesContext; 6 | import org.springframework.test.context.ContextConfiguration; 7 | import org.springframework.test.context.web.WebAppConfiguration; 8 | 9 | @WebAppConfiguration 10 | @ContextConfiguration(classes = SampleIntegrationTest.Configuration.class) 11 | @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS) 12 | public class SampleDirtiesContextBeforeClassTest extends AbstractIntegrationTest { 13 | 14 | @Test 15 | public void test() { 16 | TestEventTracker.trackEvent("Running " + getClass().getSimpleName() + ".test"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 4 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.TestPropertySource; 12 | import org.springframework.test.context.web.WebAppConfiguration; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; 15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 16 | import org.springframework.web.context.WebApplicationContext; 17 | 18 | @WebAppConfiguration 19 | @ContextConfiguration(classes = { 20 | SampleIntegrationTest.Configuration.class 21 | }) 22 | @ActiveProfiles("test") 23 | @TestPropertySource(properties = { 24 | "parameter = value" 25 | }) 26 | public class SampleIntegrationTest extends AbstractIntegrationTest { 27 | 28 | @Autowired 29 | private MockMvc mockMvc; 30 | 31 | @Test 32 | public void test404() throws Exception { 33 | mockMvc.perform(get("/article")) 34 | .andExpect(status().isNotFound()); 35 | } 36 | 37 | public static class Configuration { 38 | 39 | @Bean 40 | public MockMvc mockMvc(WebApplicationContext webApplicationContext) { 41 | DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(webApplicationContext); 42 | return builder.build(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/SmartDirtiesJupiterTestsSorterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.github.seregamorph.testsmartcontext.SmartDirtiesTestsSorter; 6 | import com.github.seregamorph.testsmartcontext.TestClassExtractor; 7 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import org.junit.jupiter.api.Test; 11 | 12 | public class SmartDirtiesJupiterTestsSorterTest { 13 | 14 | @Test 15 | public void shouldSortMostlyAlphabeticallyAndGroupSameConfigurations() { 16 | TestEventTracker.trackEvent("Started " + getClass().getSimpleName() + 17 | ".shouldSortMostlyAlphabeticallyAndGroupSameConfigurations"); 18 | // mostly: @DirtiesContext should go last. 19 | SmartDirtiesTestsSorter sorter = SmartDirtiesTestsSorter.getInstance(); 20 | var testItems = Arrays.asList( 21 | Integration2Test.class, 22 | SampleIntegrationTest.class, 23 | Integration1Test.class, 24 | Unit1Test.class, 25 | SampleDirtiesContextAfterClassTest.class, 26 | SampleDirtiesContextBeforeClassTest.class, 27 | SmartDirtiesJupiterTestsSorterTest.class 28 | ); 29 | var itClassesLists = sorter.sort(testItems, TestClassExtractor.ofClass(testClass -> testClass)); 30 | 31 | assertEquals(Arrays.asList( 32 | // UT 33 | SmartDirtiesJupiterTestsSorterTest.class, 34 | Unit1Test.class, 35 | // IT 1 36 | Integration1Test.class, 37 | // IT 2 38 | Integration2Test.class, 39 | // IT 3 40 | SampleDirtiesContextBeforeClassTest.class, 41 | SampleDirtiesContextAfterClassTest.class, 42 | // IT 4 43 | SampleIntegrationTest.class 44 | ), testItems); 45 | 46 | assertEquals(List.of( 47 | List.of(Integration1Test.class), 48 | List.of(Integration2Test.class), 49 | List.of(SampleDirtiesContextBeforeClassTest.class, 50 | SampleDirtiesContextAfterClassTest.class), 51 | List.of(SampleIntegrationTest.class) 52 | ), itClassesLists.getSortedConfigToTests()); 53 | 54 | TestEventTracker.trackEvent("Finished " + getClass().getSimpleName() + 55 | ".shouldSortMostlyAlphabeticallyAndGroupSameConfigurations"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/java/com/github/seregamorph/testsmartcontext/demo/Unit1Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 6 | import org.junit.jupiter.api.Test; 7 | 8 | public class Unit1Test { 9 | 10 | @Test 11 | public void test() { 12 | TestEventTracker.trackEvent("Running " + getClass().getName() + ".test"); 13 | assertTrue(true); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.extensions.autodetection.enabled=true 2 | junit.jupiter.testclass.order.default=com.github.seregamorph.testsmartcontext.jupiter.SmartDirtiesClassOrderer 3 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring5/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-maven-junit-platform-jupiter-spring7 14 | 15 | Demo project with tests launched via Maven junit-platform jupiter on bare spring 7 16 | 17 | 18 | true 19 | 20 | 7.0.0-M5 21 | 1.5.12 22 | 1.11.3 23 | 5.11.3 24 | 6.1.0 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-surefire-plugin 32 | 33 | 34 | org.apache.maven.surefire 35 | surefire-junit-platform 36 | ${maven-surefire.version} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework 46 | spring-webmvc 47 | ${spring-framework.version} 48 | 49 | 50 | jakarta.servlet 51 | jakarta.servlet-api 52 | ${jakarta.servlet-api.version} 53 | 54 | 55 | 56 | ch.qos.logback 57 | logback-classic 58 | ${logback.version} 59 | 60 | 61 | org.junit.jupiter 62 | junit-jupiter 63 | ${junit-jupiter.version} 64 | test 65 | 66 | 67 | org.junit.platform 68 | junit-platform-testkit 69 | ${junit-platform.version} 70 | test 71 | 72 | 73 | 74 | 75 | com.github.seregamorph 76 | spring-test-smart-context 77 | test 78 | 79 | 80 | com.github.seregamorph 81 | demo-testkit 82 | test 83 | 84 | 85 | 86 | org.springframework 87 | spring-test 88 | ${spring-framework.version} 89 | test 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/SmartDirtiesTestsSupportTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Nested; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.TestPropertySource; 12 | import org.springframework.test.context.junit.jupiter.SpringExtension; 13 | 14 | class SmartDirtiesTestsSupportTest { 15 | 16 | @BeforeEach 17 | public void prepare() { 18 | SmartDirtiesTestsSupport.setTestClassesLists("SmartDirtiesTestsHolderTest", 19 | TestSortResult.singletonList(TestRootTest.class)); 20 | } 21 | 22 | @Test 23 | public void nestedInheritShouldPass() { 24 | SmartDirtiesTestsSupport.verifyInnerClass(TestRootTest.NestedInheritTest.class); 25 | } 26 | 27 | @Test 28 | public void nestedCustomShouldFail() { 29 | var ise = Assertions.assertThrows(IllegalStateException.class, 30 | () -> SmartDirtiesTestsSupport.verifyInnerClass(TestRootTest.NestedCustomTest.class)); 31 | assertEquals("Nested inner class com.github.seregamorph.testsmartcontext.TestRootTest$NestedCustomTest " + 32 | "declares custom context configuration which differs from enclosing class com.github.seregamorph" + 33 | ".testsmartcontext.TestRootTest. This is not properly supported by the spring-test-smart-context ordering" + 34 | " because of framework limitations. Please extract inner test class to upper level.", ise.getMessage()); 35 | } 36 | } 37 | 38 | @ExtendWith(SpringExtension.class) 39 | @ContextConfiguration(classes = { 40 | TestRootTest.Configuration.class 41 | }) 42 | class TestRootTest { 43 | 44 | public static class Configuration { 45 | 46 | } 47 | 48 | @Nested 49 | public class NestedInheritTest { 50 | } 51 | 52 | @Nested 53 | @TestPropertySource(properties = { 54 | "parameter = value" 55 | }) 56 | public class NestedCustomTest { 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import com.github.seregamorph.testsmartcontext.jupiter.AbstractJUnitSpringIntegrationTest; 4 | import org.junit.jupiter.api.BeforeEach; 5 | 6 | public abstract class AbstractIntegrationTest extends AbstractJUnitSpringIntegrationTest { 7 | 8 | @BeforeEach 9 | public void beforeMethod() { 10 | System.out.println("Running " + getClass()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration1Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Nested; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Import; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.web.WebAppConfiguration; 9 | 10 | @WebAppConfiguration 11 | @ContextConfiguration(classes = { 12 | Integration1Test.Configuration.class 13 | }) 14 | public class Integration1Test extends AbstractIntegrationTest { 15 | 16 | @Autowired 17 | private SampleBean rootBean; 18 | 19 | @Test 20 | public void test() { 21 | System.out.println("Integration1Test.test " + rootBean); 22 | } 23 | 24 | @Nested 25 | public class NestedTest { 26 | 27 | @Autowired 28 | private SampleBean nestedBean; 29 | 30 | @Test 31 | public void nested() { 32 | System.out.println("Integration1Test.NestedTest.test " + nestedBean); 33 | } 34 | 35 | @Nested 36 | public class DeeplyNestedTest { 37 | 38 | @Autowired 39 | private SampleBean deeplyNestedBean; 40 | 41 | @Test 42 | public void deeplyNested() { 43 | System.out.println("Integration1Test.NestedTest.DeeplyNestedTest.deeplyNested " + deeplyNestedBean); 44 | } 45 | } 46 | } 47 | 48 | @Import(SampleBean.class) 49 | public static class Configuration { 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration2Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Nested; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.test.context.ContextConfiguration; 6 | 7 | @ContextConfiguration(classes = { 8 | Integration2Test.Configuration.class 9 | }) 10 | public class Integration2Test extends AbstractIntegrationTest { 11 | 12 | @Test 13 | public void test() { 14 | System.out.println("Integration2Test.test"); 15 | } 16 | 17 | @Nested 18 | public class NestedTest { 19 | 20 | @Test 21 | public void nested() { 22 | System.out.println("Integration2Test.NestedTest.test"); 23 | } 24 | } 25 | 26 | public static class Configuration { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleBean.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | public class SampleBean { 4 | } 5 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleDirtiesContextAfterClassTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.test.annotation.DirtiesContext; 5 | import org.springframework.test.context.ContextConfiguration; 6 | import org.springframework.test.context.web.WebAppConfiguration; 7 | 8 | @WebAppConfiguration 9 | @ContextConfiguration(classes = SampleIntegrationTest.Configuration.class) 10 | @DirtiesContext 11 | public class SampleDirtiesContextAfterClassTest extends AbstractIntegrationTest { 12 | 13 | @Test 14 | public void test() { 15 | System.out.println("Running " + getClass().getName() + ".test"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleDirtiesContextBeforeClassTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.test.annotation.DirtiesContext; 6 | import org.springframework.test.context.ContextConfiguration; 7 | import org.springframework.test.context.web.WebAppConfiguration; 8 | 9 | @WebAppConfiguration 10 | @ContextConfiguration(classes = SampleIntegrationTest.Configuration.class) 11 | @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS) 12 | public class SampleDirtiesContextBeforeClassTest extends AbstractIntegrationTest { 13 | 14 | @Test 15 | public void test() { 16 | TestEventTracker.trackEvent("Running " + getClass().getSimpleName() + ".test"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/SampleIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 4 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.TestPropertySource; 12 | import org.springframework.test.context.web.WebAppConfiguration; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; 15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 16 | import org.springframework.web.context.WebApplicationContext; 17 | 18 | @WebAppConfiguration 19 | @ContextConfiguration(classes = { 20 | SampleIntegrationTest.Configuration.class 21 | }) 22 | @ActiveProfiles("test") 23 | @TestPropertySource(properties = { 24 | "parameter = value" 25 | }) 26 | public class SampleIntegrationTest extends AbstractIntegrationTest { 27 | 28 | @Autowired 29 | private MockMvc mockMvc; 30 | 31 | @Test 32 | public void test404() throws Exception { 33 | mockMvc.perform(get("/article")) 34 | .andExpect(status().isNotFound()); 35 | } 36 | 37 | public static class Configuration { 38 | 39 | @Bean 40 | public MockMvc mockMvc(WebApplicationContext webApplicationContext) { 41 | DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(webApplicationContext); 42 | return builder.build(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/SmartDirtiesJupiterTestsSorterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.github.seregamorph.testsmartcontext.SmartDirtiesTestsSorter; 6 | import com.github.seregamorph.testsmartcontext.TestClassExtractor; 7 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import org.junit.jupiter.api.Test; 11 | 12 | public class SmartDirtiesJupiterTestsSorterTest { 13 | 14 | @Test 15 | public void shouldSortMostlyAlphabeticallyAndGroupSameConfigurations() { 16 | TestEventTracker.trackEvent("Started " + getClass().getSimpleName() + 17 | ".shouldSortMostlyAlphabeticallyAndGroupSameConfigurations"); 18 | // mostly: @DirtiesContext should go last. 19 | SmartDirtiesTestsSorter sorter = SmartDirtiesTestsSorter.getInstance(); 20 | var testItems = Arrays.asList( 21 | Integration2Test.class, 22 | SampleIntegrationTest.class, 23 | Integration1Test.class, 24 | Unit1Test.class, 25 | SampleDirtiesContextAfterClassTest.class, 26 | SampleDirtiesContextBeforeClassTest.class, 27 | SmartDirtiesJupiterTestsSorterTest.class 28 | ); 29 | var itClassesLists = sorter.sort(testItems, TestClassExtractor.ofClass(testClass -> testClass)); 30 | 31 | assertEquals(Arrays.asList( 32 | // UT 33 | SmartDirtiesJupiterTestsSorterTest.class, 34 | Unit1Test.class, 35 | // IT 1 36 | Integration1Test.class, 37 | // IT 2 38 | Integration2Test.class, 39 | // IT 3 40 | SampleDirtiesContextBeforeClassTest.class, 41 | SampleDirtiesContextAfterClassTest.class, 42 | // IT 4 43 | SampleIntegrationTest.class 44 | ), testItems); 45 | 46 | assertEquals(List.of( 47 | List.of(Integration1Test.class), 48 | List.of(Integration2Test.class), 49 | List.of(SampleDirtiesContextBeforeClassTest.class, 50 | SampleDirtiesContextAfterClassTest.class), 51 | List.of(SampleIntegrationTest.class) 52 | ), itClassesLists.getSortedConfigToTests()); 53 | 54 | TestEventTracker.trackEvent("Finished " + getClass().getSimpleName() + 55 | ".shouldSortMostlyAlphabeticallyAndGroupSameConfigurations"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/java/com/github/seregamorph/testsmartcontext/demo/Unit1Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 6 | import org.junit.jupiter.api.Test; 7 | 8 | public class Unit1Test { 9 | 10 | @Test 11 | public void test() { 12 | TestEventTracker.trackEvent("Running " + getClass().getName() + ".test"); 13 | assertTrue(true); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-jupiter-spring7/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-mixed-boot34/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-maven-junit-platform-mixed-boot34 14 | 15 | Demo project with mixed tests JUnit 4, JUnit 5 and TestNG 16 | launched via Maven junit-platform jupiter 17 | 18 | 19 | 3.4.1 20 | 1.0.5 21 | true 22 | 23 | 24 | 25 | 26 | 27 | org.apache.maven.plugins 28 | maven-surefire-plugin 29 | 30 | 31 | org.apache.maven.surefire 32 | surefire-junit-platform 33 | ${maven-surefire.version} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-dependencies 45 | ${spring-boot.version} 46 | pom 47 | import 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-web 56 | 57 | 58 | 59 | 60 | com.github.seregamorph 61 | spring-test-smart-context 62 | test 63 | 64 | 65 | com.github.seregamorph 66 | demo-testkit 67 | test 68 | 69 | 70 | 71 | org.junit.platform 72 | junit-platform-testkit 73 | test 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-starter-test 79 | test 80 | 81 | 82 | org.junit.vintage 83 | junit-vintage-engine 84 | test 85 | 86 | 87 | org.junit.support 88 | testng-engine 89 | ${testng-engine.version} 90 | test 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-mixed-boot34/src/test/java/com/github/seregamorph/testsmartcontext/MavenSmartDirtiesJupiterEngineTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import static org.junit.platform.engine.discovery.ClassNameFilter.excludeClassNamePatterns; 7 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; 8 | import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; 9 | 10 | import com.github.seregamorph.testsmartcontext.demo.ExtendWithTest; 11 | import com.github.seregamorph.testsmartcontext.demo.Integration2SpringJUnitConfigTest; 12 | import com.github.seregamorph.testsmartcontext.demo.NoBaseClass1IntegrationTest; 13 | import com.github.seregamorph.testsmartcontext.demo.NoBaseClass2IntegrationTest; 14 | import com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest; 15 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | import org.junit.jupiter.api.AfterAll; 20 | import org.junit.jupiter.api.BeforeAll; 21 | import org.junit.jupiter.api.Test; 22 | import org.junit.platform.testkit.engine.EngineTestKit; 23 | 24 | public class MavenSmartDirtiesJupiterEngineTest { 25 | 26 | private static final String ENGINE = "junit-jupiter"; 27 | 28 | private static Map, SmartDirtiesTestsSupport.ClassOrderState>> prevEngineClassOrderStateMap; 29 | 30 | @BeforeAll 31 | public static void beforeClass() { 32 | prevEngineClassOrderStateMap = SmartDirtiesTestsSupport.setEngineClassOrderStateMap(null); 33 | TestEventTracker.startTracking(); 34 | } 35 | 36 | @AfterAll 37 | public static void afterClass() { 38 | TestEventTracker.stopTracking(); 39 | SmartDirtiesTestsSupport.setEngineClassOrderStateMap(prevEngineClassOrderStateMap); 40 | } 41 | 42 | @Test 43 | public void testSuite() { 44 | // to avoid confusion of duplicated test execution output 45 | System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 46 | System.out.println(">>>EngineTestKit duplicating the suite>>>"); 47 | 48 | var events = EngineTestKit.execute(ENGINE, request() 49 | .selectors(selectPackage("com.github.seregamorph.testsmartcontext.demo")) 50 | .filters(excludeClassNamePatterns(getClass().getName())) 51 | .build()) 52 | .containerEvents(); 53 | 54 | // 7 = 5 ITs + 1 UTs + 1 suite 55 | events.assertStatistics(stats -> stats 56 | .started(7) 57 | .succeeded(7) 58 | .finished(7) 59 | .aborted(0) 60 | .failed(0)); 61 | 62 | assertEquals(List.of( 63 | ExtendWithTest.class, 64 | NoBaseClass1IntegrationTest.class, 65 | NoBaseClass2IntegrationTest.class, 66 | Integration2SpringJUnitConfigTest.class, 67 | SampleIntegrationTest.class 68 | ), new ArrayList<>(TestSmartDirtiesTestsHolder.getIntegrationTestClasses(ENGINE))); 69 | 70 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(ExtendWithTest.class)); 71 | assertFalse(SmartDirtiesTestsSupport.isFirstClassPerConfig(NoBaseClass1IntegrationTest.class)); 72 | assertFalse(SmartDirtiesTestsSupport.isFirstClassPerConfig(NoBaseClass2IntegrationTest.class)); 73 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration2SpringJUnitConfigTest.class)); 74 | assertFalse(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration2SpringJUnitConfigTest.NestedTest.class)); 75 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(SampleIntegrationTest.class)); 76 | 77 | assertFalse(SmartDirtiesTestsSupport.isLastClassPerConfig(ExtendWithTest.class)); 78 | assertFalse(SmartDirtiesTestsSupport.isLastClassPerConfig(NoBaseClass1IntegrationTest.class)); 79 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(NoBaseClass2IntegrationTest.class)); 80 | assertFalse(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration2SpringJUnitConfigTest.NestedTest.class)); 81 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration2SpringJUnitConfigTest.class)); 82 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(SampleIntegrationTest.class)); 83 | 84 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.ExtendWithTest"); 85 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.ExtendWithTest"); 86 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.NoBaseClass2IntegrationTest"); 87 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration2SpringJUnitConfigTest"); 88 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration2SpringJUnitConfigTest"); 89 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration2SpringJUnitConfigTest"); 90 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 91 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 92 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.SampleIntegrationTest"); 93 | TestEventTracker.assertEmpty(); 94 | 95 | System.out.println("<<, SmartDirtiesTestsSupport.ClassOrderState>> prevEngineClassOrderStateMap; 24 | 25 | @BeforeAll 26 | public static void beforeClass() { 27 | prevEngineClassOrderStateMap = SmartDirtiesTestsSupport.setEngineClassOrderStateMap(null); 28 | TestEventTracker.startTracking(); 29 | } 30 | 31 | @AfterAll 32 | public static void afterClass() { 33 | TestEventTracker.stopTracking(); 34 | SmartDirtiesTestsSupport.setEngineClassOrderStateMap(prevEngineClassOrderStateMap); 35 | } 36 | 37 | @Test 38 | public void testSuite() { 39 | // to avoid confusion of duplicated test execution output 40 | System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 41 | System.out.println(">>>EngineTestKit duplicating the suite>>>"); 42 | 43 | var events = EngineTestKit.execute(ENGINE, request() 44 | .selectors(selectPackage("com.github.seregamorph.testsmartcontext.demo")) 45 | .build()) 46 | .containerEvents(); 47 | 48 | // 3 = 2 ITs + 0 UTs + 1 suite 49 | events.assertStatistics(stats -> stats 50 | .started(3) 51 | .succeeded(3) 52 | .finished(3) 53 | .aborted(0) 54 | .failed(0)); 55 | 56 | assertEquals(List.of( 57 | Integration1Test.class, 58 | SampleDirtiesContextAfterClassTest.class 59 | ), new ArrayList<>(TestSmartDirtiesTestsHolder.getIntegrationTestClasses(ENGINE))); 60 | 61 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration1Test.class)); 62 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(SampleDirtiesContextAfterClassTest.class)); 63 | 64 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration1Test.class)); 65 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(SampleDirtiesContextAfterClassTest.class)); 66 | 67 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 68 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 69 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 70 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.SampleDirtiesContextAfterClassTest"); 71 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.SampleDirtiesContextAfterClassTest"); 72 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.SampleDirtiesContextAfterClassTest"); 73 | TestEventTracker.assertEmpty(); 74 | 75 | System.out.println("<<, SmartDirtiesTestsSupport.ClassOrderState>> prevEngineClassOrderStateMap; 24 | 25 | @BeforeAll 26 | public static void beforeClass() { 27 | prevEngineClassOrderStateMap = SmartDirtiesTestsSupport.setEngineClassOrderStateMap(null); 28 | TestEventTracker.startTracking(); 29 | } 30 | 31 | @AfterAll 32 | public static void afterClass() { 33 | TestEventTracker.stopTracking(); 34 | SmartDirtiesTestsSupport.setEngineClassOrderStateMap(prevEngineClassOrderStateMap); 35 | } 36 | 37 | @Test 38 | public void testSuite() { 39 | // to avoid confusion of duplicated test execution output 40 | System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 41 | System.out.println(">>>EngineTestKit duplicating the suite>>>"); 42 | 43 | var events = EngineTestKit.execute(ENGINE, request() 44 | .selectors(selectPackage("com.github.seregamorph.testsmartcontext.demo")) 45 | .filters(new SmartDirtiesPostDiscoveryFilter()) 46 | .build()) 47 | .containerEvents(); 48 | 49 | // 4 = 2 ITs + 1 UTs + 1 suite 50 | events.assertStatistics(stats -> stats 51 | .started(4) 52 | .succeeded(4) 53 | .finished(4) 54 | .aborted(0) 55 | .failed(0)); 56 | 57 | assertEquals(List.of( 58 | Integration2Test.class, 59 | SampleDirtiesContextBeforeClassTest.class 60 | ), new ArrayList<>(TestSmartDirtiesTestsHolder.getIntegrationTestClasses(ENGINE))); 61 | 62 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration2Test.class)); 63 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(SampleDirtiesContextBeforeClassTest.class)); 64 | 65 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration2Test.class)); 66 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(SampleDirtiesContextBeforeClassTest.class)); 67 | 68 | TestEventTracker.assertConsumedEvent("Running com.github.seregamorph.testsmartcontext.demo.Unit1Test.test"); 69 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 70 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 71 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 72 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.SampleDirtiesContextBeforeClassTest"); 73 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.SampleDirtiesContextBeforeClassTest"); 74 | TestEventTracker.assertConsumedEvent("Running SampleDirtiesContextBeforeClassTest.test"); 75 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.SampleDirtiesContextBeforeClassTest"); 76 | TestEventTracker.assertEmpty(); 77 | 78 | System.out.println("<< 2 | 3 | 4 | %d{HH:mm:ss} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-testng-boot32/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-maven-junit-platform-testng-boot32 14 | 15 | Demo project with tests launched via Maven junit-platform testng-engine 16 | 17 | 18 | 3.2.2 19 | 1.0.5 20 | true 21 | 22 | 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-surefire-plugin 28 | 29 | 30 | org.apache.maven.surefire 31 | surefire-junit-platform 32 | ${maven-surefire.version} 33 | 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-failsafe-plugin 39 | 40 | 41 | org.apache.maven.surefire 42 | surefire-junit-platform 43 | ${maven-surefire.version} 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-dependencies 55 | ${spring-boot.version} 56 | pom 57 | import 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-web 66 | 67 | 68 | 69 | 70 | com.github.seregamorph 71 | spring-test-smart-context 72 | test 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-test 78 | test 79 | 80 | 81 | org.junit.jupiter 82 | junit-jupiter 83 | 84 | 85 | org.mockito 86 | mockito-junit-jupiter 87 | 88 | 89 | 90 | 91 | org.junit.platform 92 | junit-platform-launcher 93 | test 94 | 95 | 96 | org.junit.support 97 | testng-engine 98 | ${testng-engine.version} 99 | test 100 | 101 | 102 | org.junit.platform 103 | junit-platform-testkit 104 | test 105 | 106 | 107 | com.github.seregamorph 108 | demo-testkit 109 | test 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /demo/demo-maven-junit-platform-testng-boot32/src/test/java/com/github/seregamorph/testsmartcontext/SmartDirtiesTestngEngineTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; 4 | import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; 5 | import static org.testng.AssertJUnit.assertEquals; 6 | import static org.testng.AssertJUnit.assertFalse; 7 | import static org.testng.AssertJUnit.assertTrue; 8 | 9 | import com.github.seregamorph.testsmartcontext.demo.Integration1IT; 10 | import com.github.seregamorph.testsmartcontext.demo.Integration1SecondIT; 11 | import com.github.seregamorph.testsmartcontext.demo.Integration2IT; 12 | import com.github.seregamorph.testsmartcontext.demo.SampleIT; 13 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | import org.junit.platform.testkit.engine.EngineTestKit; 18 | import org.testng.annotations.AfterClass; 19 | import org.testng.annotations.BeforeClass; 20 | import org.testng.annotations.Test; 21 | 22 | public class SmartDirtiesTestngEngineTest { 23 | 24 | private static final String ENGINE = "testng"; 25 | 26 | private static Map, SmartDirtiesTestsSupport.ClassOrderState>> prevEngineClassOrderStateMap; 27 | 28 | @BeforeClass 29 | public static void beforeClass() { 30 | prevEngineClassOrderStateMap = SmartDirtiesTestsSupport.setEngineClassOrderStateMap(null); 31 | TestEventTracker.startTracking(); 32 | } 33 | 34 | @AfterClass 35 | public static void afterClass() { 36 | TestEventTracker.stopTracking(); 37 | SmartDirtiesTestsSupport.setEngineClassOrderStateMap(prevEngineClassOrderStateMap); 38 | } 39 | 40 | @Test 41 | public void testSuite() { 42 | // to avoid confusion of duplicated test execution output 43 | System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 44 | System.out.println(">>>EngineTestKit duplicating the suite>>>"); 45 | 46 | var events = EngineTestKit.execute(ENGINE, request() 47 | .selectors(selectPackage("com.github.seregamorph.testsmartcontext.demo")) 48 | .build()) 49 | .containerEvents(); 50 | 51 | // 10 = 7 ITs + 2 UTs + 1 suite 52 | events.assertStatistics(stats -> stats 53 | .started(6) 54 | .succeeded(6) 55 | .finished(6) 56 | .aborted(0) 57 | .failed(0)); 58 | 59 | assertEquals(List.of( 60 | Integration1SecondIT.class, 61 | Integration1IT.class, 62 | Integration2IT.class, 63 | SampleIT.class 64 | ), new ArrayList<>(TestSmartDirtiesTestsHolder.getIntegrationTestClasses(ENGINE))); 65 | 66 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration1SecondIT.class)); 67 | assertFalse(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration1IT.class)); 68 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(Integration2IT.class)); 69 | assertTrue(SmartDirtiesTestsSupport.isFirstClassPerConfig(SampleIT.class)); 70 | 71 | assertFalse(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration1SecondIT.class)); 72 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration1IT.class)); 73 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(Integration2IT.class)); 74 | assertTrue(SmartDirtiesTestsSupport.isLastClassPerConfig(SampleIT.class)); 75 | 76 | TestEventTracker.assertConsumedEvent("Running Unit1Test"); 77 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration1SecondIT"); 78 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration1SecondIT"); 79 | TestEventTracker.assertConsumedEvent("Running Integration1SecondIT"); 80 | TestEventTracker.assertConsumedEvent("Running Integration1IT"); 81 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration1IT"); 82 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration2IT"); 83 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration2IT"); 84 | TestEventTracker.assertConsumedEvent("Running Integration2IT"); 85 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration2IT"); 86 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.SampleIT"); 87 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.SampleIT"); 88 | TestEventTracker.assertConsumedEvent("Running SampleIT"); 89 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.SampleIT"); 90 | TestEventTracker.assertEmpty(); 91 | 92 | System.out.println("<< 2 | 3 | 4 | %d{HH:mm:ss} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/demo-maven-testng-boot24/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-maven-testng-boot24 14 | 15 | Demo project with tests launched via Maven TestNG 16 | 17 | 18 | 2.4.2 19 | true 20 | 21 | 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-surefire-plugin 27 | 28 | 29 | org.apache.maven.surefire 30 | surefire-testng 31 | ${maven-surefire.version} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-dependencies 43 | ${spring-boot.version} 44 | pom 45 | import 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | com.github.seregamorph 54 | spring-test-smart-context 55 | test 56 | 57 | 58 | 59 | org.testng 60 | testng 61 | 7.10.2 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-test 67 | test 68 | 69 | 70 | org.junit.jupiter 71 | junit-jupiter 72 | 73 | 74 | org.mockito 75 | mockito-junit-jupiter 76 | 77 | 78 | 79 | 80 | com.github.seregamorph 81 | demo-testkit 82 | test 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /demo/demo-maven-testng-boot24/src/test/java/com/github/seregamorph/testsmartcontext/MavenTestngTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 4 | import org.testng.annotations.AfterSuite; 5 | import org.testng.annotations.BeforeSuite; 6 | 7 | public class MavenTestngTest { 8 | 9 | @BeforeSuite 10 | public static void beforeSuite() { 11 | TestEventTracker.startTracking(); 12 | } 13 | 14 | @AfterSuite 15 | public static void afterSuite() { 16 | // hint: this test can fail if executed with test filtering (like run single test) 17 | TestEventTracker.assertConsumedEvent("Running Unit1Test.test"); 18 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration1SecondTest"); 19 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration1SecondTest"); 20 | TestEventTracker.assertConsumedEvent("Running Integration1SecondTest"); 21 | TestEventTracker.assertConsumedEvent("Running Integration1Test"); 22 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration1Test"); 23 | TestEventTracker.assertConsumedEvent("Creating context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 24 | TestEventTracker.assertConsumedEvent("Created context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 25 | TestEventTracker.assertConsumedEvent("Running Integration2Test"); 26 | TestEventTracker.assertConsumedEvent("Destroying context for com.github.seregamorph.testsmartcontext.demo.Integration2Test"); 27 | TestEventTracker.assertEmpty(); 28 | 29 | TestEventTracker.stopTracking(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo/demo-maven-testng-boot24/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration1SecondTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS; 4 | 5 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 6 | import com.github.seregamorph.testsmartcontext.testng.AbstractTestNGSpringIntegrationTest; 7 | import org.springframework.test.annotation.DirtiesContext; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.testng.annotations.Test; 10 | 11 | @ContextConfiguration(classes = { 12 | Integration1Test.Configuration.class 13 | }) 14 | @DirtiesContext(classMode = BEFORE_CLASS) 15 | public class Integration1SecondTest extends AbstractTestNGSpringIntegrationTest { 16 | 17 | @Test 18 | public void test() { 19 | TestEventTracker.trackEvent("Running " + getClass().getSimpleName()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/demo-maven-testng-boot24/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration1Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 4 | import com.github.seregamorph.testsmartcontext.testng.AbstractTestNGSpringIntegrationTest; 5 | import org.springframework.test.context.ContextConfiguration; 6 | import org.testng.annotations.Test; 7 | 8 | @ContextConfiguration(classes = { 9 | Integration1Test.Configuration.class 10 | }) 11 | public class Integration1Test extends AbstractTestNGSpringIntegrationTest { 12 | 13 | @Test 14 | public void test() { 15 | TestEventTracker.trackEvent("Running " + getClass().getSimpleName()); 16 | } 17 | 18 | public static class Configuration { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/demo-maven-testng-boot24/src/test/java/com/github/seregamorph/testsmartcontext/demo/Integration2Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 4 | import com.github.seregamorph.testsmartcontext.testng.AbstractTestNGSpringIntegrationTest; 5 | import org.springframework.test.context.ContextConfiguration; 6 | import org.testng.annotations.Test; 7 | 8 | @ContextConfiguration(classes = { 9 | Integration2Test.Configuration.class 10 | }) 11 | public class Integration2Test extends AbstractTestNGSpringIntegrationTest { 12 | 13 | @Test 14 | public void test() { 15 | TestEventTracker.trackEvent("Running " + getClass().getSimpleName()); 16 | } 17 | 18 | public static class Configuration { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/demo-maven-testng-boot24/src/test/java/com/github/seregamorph/testsmartcontext/demo/Unit1Test.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.demo; 2 | 3 | import static org.testng.Assert.assertTrue; 4 | 5 | import com.github.seregamorph.testsmartcontext.testkit.TestEventTracker; 6 | import org.testng.annotations.Test; 7 | 8 | public class Unit1Test { 9 | 10 | @Test 11 | public void test() { 12 | TestEventTracker.trackEvent("Running Unit1Test.test"); 13 | assertTrue(true); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/demo-maven-testng-boot24/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/demo-testkit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | demo-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-testkit 14 | 15 | Utility for integration testing of the framework 16 | 17 | 5.3.3 18 | 19 | 20 | 21 | 22 | com.github.seregamorph 23 | spring-test-smart-context 24 | 25 | 26 | 27 | org.springframework 28 | spring-context 29 | ${spring-framework.version} 30 | 31 | 32 | org.springframework 33 | spring-test 34 | ${spring-framework.version} 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /demo/demo-testkit/src/main/java/com/github/seregamorph/testsmartcontext/TestSmartDirtiesTestsHolder.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Set; 6 | import org.springframework.util.Assert; 7 | 8 | class TestSmartDirtiesTestsHolder { 9 | 10 | static List> getIntegrationTestClasses(String engine) { 11 | Set> testClasses = SmartDirtiesTestsSupport.getTestClasses(engine); 12 | List> integrationTestClasses = new ArrayList<>(); 13 | IntegrationTestFilter integrationTestFilter = IntegrationTestFilter.getInstance(); 14 | Assert.state(testClasses != null, "Test classes are not initialized"); 15 | for (Class testClass : testClasses) { 16 | if (integrationTestFilter.isIntegrationTest(testClass)) { 17 | integrationTestClasses.add(testClass); 18 | } 19 | } 20 | return integrationTestClasses; 21 | } 22 | 23 | private TestSmartDirtiesTestsHolder() { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo/demo-testkit/src/main/java/com/github/seregamorph/testsmartcontext/testkit/TestContextEventTrackerListener.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.testkit; 2 | 3 | import com.github.seregamorph.testsmartcontext.CurrentTestContext; 4 | import com.github.seregamorph.testsmartcontext.SpringContextEventLoggerListener; 5 | import org.springframework.context.event.ContextClosedEvent; 6 | import org.springframework.context.event.ContextRefreshedEvent; 7 | 8 | public class TestContextEventTrackerListener extends SpringContextEventLoggerListener { 9 | 10 | @Override 11 | protected void onCreated() { 12 | TestEventTracker.trackEvent("Creating context for " + CurrentTestContext.getCurrentTestClassName()); 13 | } 14 | 15 | @Override 16 | protected void onContextRefreshedEvent(ContextRefreshedEvent event) { 17 | super.onContextRefreshedEvent(event); 18 | boolean isChild = event.getApplicationContext().getParent() != null; 19 | TestEventTracker.trackEvent("Created " + (isChild ? "child context" : "context") + " for " 20 | + CurrentTestContext.getCurrentTestClassName()); 21 | } 22 | 23 | @Override 24 | protected void onContextClosedEvent(ContextClosedEvent event) { 25 | String currentTestClass = CurrentTestContext.getCurrentTestClassName(); 26 | boolean isChild = event.getApplicationContext().getParent() != null; 27 | if (currentTestClass == null) { 28 | // system shutdown hook 29 | TestEventTracker.trackEvent("Destroying " + (isChild ? "child context" : "context") + " (hook)"); 30 | } else { 31 | // triggered via SmartDirtiesContextTestExecutionListener or spring DirtiesContextTestExecutionListener 32 | TestEventTracker.trackEvent("Destroying " + (isChild ? "child context" : "context") + " for " + currentTestClass); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/demo-testkit/src/main/java/com/github/seregamorph/testsmartcontext/testkit/TestEventTracker.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.testkit; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | public class TestEventTracker { 7 | 8 | private static final ThreadLocal currentEventTracker = new ThreadLocal<>(); 9 | 10 | private final Queue events = new LinkedList<>(); 11 | 12 | public static void startTracking() { 13 | currentEventTracker.set(new TestEventTracker()); 14 | } 15 | 16 | public static void stopTracking() { 17 | currentEventTracker.remove(); 18 | } 19 | 20 | public static void trackEvent(String event) { 21 | var testEventTracker = currentEventTracker.get(); 22 | if (testEventTracker != null) { 23 | System.out.println("Tracked event: " + event); 24 | testEventTracker.events.add(event); 25 | } else { 26 | System.out.println("Event: " + event); 27 | } 28 | } 29 | 30 | public static void assertConsumedEvent(String expected) { 31 | var testEventTracker = currentEventTracker.get(); 32 | if (testEventTracker == null) { 33 | throw new IllegalStateException("currentEventTracker not initialized"); 34 | } 35 | var actual = testEventTracker.events.poll(); 36 | if (!expected.equals(actual)) { 37 | throw new AssertionError("Expected [" + expected + "] but got [" + actual + "], " + 38 | "leftover events: " + testEventTracker.events); 39 | } 40 | } 41 | 42 | public static void assertEmpty() { 43 | var testEventTracker = currentEventTracker.get(); 44 | if (testEventTracker == null) { 45 | throw new IllegalStateException("currentEventTracker not initialized"); 46 | } 47 | if (!testEventTracker.events.isEmpty()) { 48 | throw new AssertionError("Expected empty, but found leftover events: " + testEventTracker.events); 49 | } 50 | } 51 | 52 | private TestEventTracker() { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /demo/demo-testkit/src/main/resources/META-INF/services/com.github.seregamorph.testsmartcontext.SpringContextEventLoggerListener: -------------------------------------------------------------------------------- 1 | com.github.seregamorph.testsmartcontext.testkit.TestContextEventTrackerListener 2 | -------------------------------------------------------------------------------- /demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | spring-test-smart-context-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | demo-parent 14 | pom 15 | 16 | 17 | 17 18 | 17 19 | 20 | true 21 | true 22 | true 23 | true 24 | 25 | 26 | 27 | demo-maven-junit-platform-junit4-boot24 28 | demo-maven-junit-platform-jupiter-boot35 29 | demo-maven-junit-platform-jupiter-spring5 30 | demo-maven-junit-platform-jupiter-spring7 31 | demo-maven-junit-platform-mixed-boot34 32 | demo-maven-junit-platform-testng-boot32 33 | demo-maven-testng-boot24 34 | demo-testkit 35 | 36 | 37 | 38 | 39 | include-gradle-build 40 | 41 | demo-gradle-junit-platform-jupiter-boot32 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | com.github.seregamorph 50 | spring-test-smart-context 51 | ${project.version} 52 | 53 | 54 | 55 | com.github.seregamorph 56 | demo-testkit 57 | ${project.version} 58 | 59 | 60 | 61 | 62 | 63 | 64 | spring-milestones 65 | https://repo.spring.io/milestone/ 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /doc/active-docker-containers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregamorph/spring-test-smart-context/7fd2b8420f2aeaf1d6cf1815dc0898981abbb6e1/doc/active-docker-containers.png -------------------------------------------------------------------------------- /doc/miro-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregamorph/spring-test-smart-context/7fd2b8420f2aeaf1d6cf1815dc0898981abbb6e1/doc/miro-logo.png -------------------------------------------------------------------------------- /doc/reorder-and-smart-dirties-context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregamorph/spring-test-smart-context/7fd2b8420f2aeaf1d6cf1815dc0898981abbb6e1/doc/reorder-and-smart-dirties-context.png -------------------------------------------------------------------------------- /doc/sample-test-suite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seregamorph/spring-test-smart-context/7fd2b8420f2aeaf1d6cf1815dc0898981abbb6e1/doc/sample-test-suite.png -------------------------------------------------------------------------------- /spring-test-smart-context/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.seregamorph 9 | spring-test-smart-context-parent 10 | 0.14-SNAPSHOT 11 | 12 | 13 | spring-test-smart-context 14 | 15 | 16 | Improving Spring Boot test efficiency. See the readme at 17 | https://github.com/seregamorph/spring-test-smart-context 18 | 19 | 20 | 21 | 1.7.36 22 | 6.1.16 23 | 7.10.2 24 | 1.11.3 25 | 5.11.3 26 | 1.0.5 27 | 28 | 29 | 30 | 31 | seregamorph 32 | Sergey Chernov 33 | serega.morph[at]gmail.com 34 | 35 | 36 | 37 | 38 | 39 | Apache License, Version 2.0 40 | http://www.apache.org/licenses/LICENSE-2.0 41 | repo 42 | 43 | 44 | 45 | https://github.com/seregamorph/spring-test-smart-context 46 | 47 | 48 | https://github.com/seregamorph/spring-test-smart-context 49 | scm:git:git@github.com:seregamorph/spring-test-smart-context.git 50 | scm:git:git@github.com:seregamorph/spring-test-smart-context.git 51 | 52 | 53 | 54 | 55 | org.slf4j 56 | slf4j-api 57 | ${slf4j.version} 58 | 59 | 60 | org.springframework 61 | spring-jdbc 62 | ${spring.version} 63 | provided 64 | 65 | 66 | 67 | org.junit.jupiter 68 | junit-jupiter-engine 69 | ${junit-jupiter.version} 70 | provided 71 | 72 | 73 | org.junit.vintage 74 | junit-vintage-engine 75 | ${junit-jupiter.version} 76 | provided 77 | 78 | 79 | org.junit.support 80 | testng-engine 81 | ${testng-engine.version} 82 | provided 83 | 84 | 85 | org.junit.platform 86 | junit-platform-launcher 87 | ${junit-platform.version} 88 | provided 89 | 90 | 91 | 92 | org.springframework 93 | spring-context 94 | ${spring.version} 95 | provided 96 | 97 | 98 | org.springframework 99 | spring-test 100 | ${spring.version} 101 | provided 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/CurrentTestContext.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import java.util.Stack; 4 | import org.springframework.lang.Nullable; 5 | 6 | /** 7 | * Utility class to obtain current integration test class (executed by 8 | * {@link org.springframework.test.context.junit.jupiter.SpringExtension}, 9 | * {@link org.springframework.test.context.junit4.SpringJUnit4ClassRunner} or extending 10 | * {@link org.springframework.test.context.testng.AbstractTestNGSpringContextTests} depending on test framework). 11 | * 12 | * @author Sergey Chernov 13 | */ 14 | public class CurrentTestContext { 15 | 16 | private static final ThreadLocal>> currentTestClass = new ThreadLocal<>(); 17 | 18 | /** 19 | * Get current test class name. It's only defined for integration test classes and 20 | * for the same thread as test engine. 21 | */ 22 | @Nullable 23 | public static String getCurrentTestClassName() { 24 | Stack> stack = currentTestClass.get(); 25 | return stack == null ? null : stack.peek().getName(); 26 | } 27 | 28 | static void pushCurrentTestClass(Class testClass) { 29 | Stack> stack = currentTestClass.get(); 30 | if (stack == null) { 31 | stack = new Stack<>(); 32 | currentTestClass.set(stack); 33 | } 34 | stack.push(testClass); 35 | } 36 | 37 | static void popCurrentTestClass() { 38 | Stack> stack = currentTestClass.get(); 39 | if (stack != null) { 40 | stack.pop(); 41 | if (stack.isEmpty()) { 42 | currentTestClass.remove(); 43 | } 44 | } 45 | } 46 | 47 | private CurrentTestContext() { 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/IntegrationTestFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.Arrays; 5 | import java.util.Iterator; 6 | import java.util.ServiceLoader; 7 | import java.util.Set; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.junit.runner.RunWith; 10 | import org.junit.runner.Runner; 11 | import org.springframework.context.ApplicationContextAware; 12 | import org.springframework.core.annotation.AnnotatedElementUtils; 13 | import org.springframework.test.context.junit.jupiter.SpringExtension; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | 16 | /** 17 | * Integration Test class filter that should be ordered (as they use spring context). The logic of this class can be 18 | * customized via 19 | *
 20 |  * META-INF/services/com.github.seregamorph.testsmartcontext.IntegrationTestFilter
 21 |  * 
22 | * defining subtype of this class overriding methods. 23 | * 24 | * @author Sergey Chernov 25 | */ 26 | public class IntegrationTestFilter { 27 | 28 | private static final IntegrationTestFilter instance = initInstance(); 29 | 30 | private static IntegrationTestFilter initInstance() { 31 | ServiceLoader serviceLoader = ServiceLoader.load(IntegrationTestFilter.class, 32 | IntegrationTestFilter.class.getClassLoader()); 33 | Iterator iterator = serviceLoader.iterator(); 34 | if (iterator.hasNext()) { 35 | return iterator.next(); 36 | } else { 37 | return new IntegrationTestFilter(); 38 | } 39 | } 40 | 41 | public static IntegrationTestFilter getInstance() { 42 | return instance; 43 | } 44 | 45 | protected IntegrationTestFilter() { 46 | } 47 | 48 | /** 49 | * Returns true if testClass is a Spring integration test class 50 | * 51 | * @param testClass 52 | * @return 53 | */ 54 | protected boolean isIntegrationTest(Class testClass) { 55 | if (Modifier.isAbstract(testClass.getModifiers())) { 56 | return false; 57 | } 58 | 59 | if (ApplicationContextAware.class.isAssignableFrom(testClass)) { 60 | // Subtypes of org.springframework.test.context.testng.AbstractTestNGSpringContextTests 61 | // and org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests 62 | return true; 63 | } 64 | 65 | if (JUnitPlatformSupport.isJunit4Present() && isIntegrationTestJUnit4(testClass)) { 66 | return true; 67 | } 68 | 69 | //noinspection RedundantIfStatement 70 | if (JUnitPlatformSupport.isJunit5JupiterApiPresent() && isIntegrationTestJUnit5Jupiter(testClass)) { 71 | return true; 72 | } 73 | 74 | return false; 75 | } 76 | 77 | /** 78 | * This method should be only called if JUnit4 is on the classpath 79 | */ 80 | protected boolean isIntegrationTestJUnit4(Class testClass) { 81 | // can be inherited, but cannot be meta-annotation 82 | RunWith runWith = testClass.getAnnotation(RunWith.class); 83 | if (runWith == null) { 84 | return false; 85 | } 86 | Class runner = runWith.value(); 87 | // includes org.springframework.test.context.junit4.SpringRunner 88 | return SpringJUnit4ClassRunner.class.isAssignableFrom(runner); 89 | } 90 | 91 | /** 92 | * This method should be only called if JUnit5 Jupiter API is on the classpath 93 | */ 94 | protected boolean isIntegrationTestJUnit5Jupiter(Class testClass) { 95 | // can be inherited, can be meta-annotation e.g. via @SpringBootTest 96 | Set extendWith = AnnotatedElementUtils.findAllMergedAnnotations(testClass, ExtendWith.class); 97 | if (extendWith.isEmpty()) { 98 | return false; 99 | } 100 | 101 | return extendWith.stream() 102 | .map(ExtendWith::value) 103 | .flatMap(Arrays::stream) 104 | .anyMatch(SpringExtension.class::isAssignableFrom); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/JUnitPlatformSupport.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import org.springframework.util.ClassUtils; 4 | 5 | /** 6 | * @author Sergey Chernov 7 | */ 8 | final class JUnitPlatformSupport { 9 | 10 | @Deprecated 11 | private static final boolean JUNIT_VINTAGE_ENGINE_PRESENT = isClassPresent( 12 | "org.junit.vintage.engine.descriptor.RunnerTestDescriptor"); 13 | 14 | @Deprecated 15 | private static final boolean JUNIT4_PRESENT = isClassPresent( 16 | "org.junit.runner.RunWith"); 17 | 18 | private static final boolean JUNIT5_JUPITER_API_PRESENT = isClassPresent( 19 | "org.junit.jupiter.api.extension.ExtendWith"); 20 | 21 | private static final boolean JUNIT4_IDEA_TEST_RUNNER_PRESENT = isClassPresent( 22 | "com.intellij.junit4.JUnit4IdeaTestRunner"); 23 | 24 | @Deprecated 25 | static boolean isJUnitVintageEnginePresent() { 26 | return JUNIT_VINTAGE_ENGINE_PRESENT; 27 | } 28 | 29 | @Deprecated 30 | static boolean isJunit4Present() { 31 | return JUNIT4_PRESENT; 32 | } 33 | 34 | static boolean isJunit5JupiterApiPresent() { 35 | return JUNIT5_JUPITER_API_PRESENT; 36 | } 37 | 38 | @Deprecated 39 | static boolean isJUnit4IdeaTestRunnerPresent() { 40 | return JUNIT4_IDEA_TEST_RUNNER_PRESENT; 41 | } 42 | 43 | private static boolean isClassPresent(String className) { 44 | return ClassUtils.isPresent(className, JUnitPlatformSupport.class.getClassLoader()); 45 | } 46 | 47 | private JUnitPlatformSupport() { 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/SmartDirtiesContextTestExecutionListener.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static com.github.seregamorph.testsmartcontext.SmartDirtiesTestsSupport.isInnerClass; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.test.context.TestContext; 8 | import org.springframework.test.context.support.AbstractTestExecutionListener; 9 | 10 | /** 11 | * Listener that works in more tricky way than spring 12 | * {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}. Based on known list (ordered) 13 | * of tests to execute (reordered via {@link com.github.seregamorph.testsmartcontext.jupiter.SmartDirtiesClassOrderer} 14 | * for Jupiter classes, {@link com.github.seregamorph.testsmartcontext.testng.SmartDirtiesSuiteListener} for TestNG 15 | * classes or {@link SmartDirtiesPostDiscoveryFilter} for JUnit 4 classes), the last test in each group that shares the 16 | * same configuration (=share the same spring context) will automatically close the ApplicationContext on after-class, 17 | * which will release resources as well (like Docker containers defined as spring beans). See detailed explanation README. 19 | * 20 | * @author Sergey Chernov 21 | * @see com.github.seregamorph.testsmartcontext.jupiter.SmartDirtiesClassOrderer 22 | * @see com.github.seregamorph.testsmartcontext.testng.SmartDirtiesSuiteListener 23 | * @see SpringContextEventLoggerListener 24 | */ 25 | public class SmartDirtiesContextTestExecutionListener extends AbstractTestExecutionListener { 26 | 27 | private static final Logger logger = LoggerFactory.getLogger(SmartDirtiesContextTestExecutionListener.class); 28 | 29 | @Override 30 | public int getOrder() { 31 | // DirtiesContextTestExecutionListener.getOrder() + 1 32 | //noinspection MagicNumber 33 | return 3001; 34 | } 35 | 36 | @Override 37 | public void beforeTestClass(TestContext testContext) { 38 | // stack Nested classes 39 | CurrentTestContext.pushCurrentTestClass(testContext.getTestClass()); 40 | Class testClass = testContext.getTestClass(); 41 | if (isInnerClass(testClass)) { 42 | SmartDirtiesTestsSupport.verifyInnerClass(testClass); 43 | } 44 | } 45 | 46 | @Override 47 | public void afterTestClass(TestContext testContext) { 48 | try { 49 | Class testClass = testContext.getTestClass(); 50 | if (SmartDirtiesTestsSupport.isLastClassPerConfig(testClass)) { 51 | logger.info("markDirty (closing context) after {}", testClass.getName()); 52 | testContext.markApplicationContextDirty(null); 53 | } else { 54 | logger.debug("Reusing context after {}", testClass.getName()); 55 | } 56 | } finally { 57 | // pop Nested classes 58 | CurrentTestContext.popCurrentTestClass(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/SmartDirtiesPostDiscoveryFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Set; 6 | import java.util.stream.Collectors; 7 | import org.junit.platform.engine.FilterResult; 8 | import org.junit.platform.engine.TestDescriptor; 9 | import org.junit.platform.engine.TestSource; 10 | import org.junit.platform.engine.support.descriptor.ClassSource; 11 | import org.junit.platform.launcher.PostDiscoveryFilter; 12 | import org.springframework.lang.NonNull; 13 | import org.springframework.lang.Nullable; 14 | 15 | /** 16 | * Auto-discovered JUnit platform {@link PostDiscoveryFilter} which reorders and groups integration test classes 17 | * according to their configuration. Note: this class sorts only JUnit 4 and Kotest tests executed via 18 | * vintage-engine 19 | * or Kotest Engine. 20 | *

21 | * For TestNG test classes - see {@link com.github.seregamorph.testsmartcontext.testng.SmartDirtiesSuiteListener}, for 22 | * Jupiter test classes - see {@link com.github.seregamorph.testsmartcontext.jupiter.SmartDirtiesClassOrderer}. 23 | * 24 | * @author Sergey Chernov 25 | */ 26 | public class SmartDirtiesPostDiscoveryFilter implements PostDiscoveryFilter { 27 | 28 | private static final List skippedEngines = Arrays.asList( 29 | SmartDirtiesTestsSupport.ENGINE_JUNIT_JUPITER, 30 | SmartDirtiesTestsSupport.ENGINE_TESTNG 31 | ); 32 | 33 | @Override 34 | public FilterResult apply(TestDescriptor testDescriptor) { 35 | String engine = testDescriptor.getUniqueId().getEngineId().orElse("undefined"); 36 | if (skippedEngines.contains(engine)) { 37 | // JUnit 5 Jupiter and TestNG have their own test ordering solutions, skip it 38 | return FilterResult.included("Skipping engine " + engine); 39 | } 40 | 41 | List childrenToReorder = testDescriptor.getChildren().stream() 42 | .filter(childTestDescriptor -> { 43 | // If it is a testng-engine running TestNG test, rely on SmartDirtiesSuiteListener, because 44 | // TestNG will alphabetically reorder it first anyway. 45 | // Jupiter engine has its own sorting via SmartDirtiesClassOrderer, so skip them as well. 46 | // Reorder only JUnit4 or Kotest here: 47 | return getTestClassOrNull(childTestDescriptor) != null; 48 | }) 49 | .collect(Collectors.toList()); 50 | 51 | if (childrenToReorder.isEmpty()) { 52 | return FilterResult.included("Empty list"); 53 | } 54 | 55 | Set> uniqueClasses = childrenToReorder.stream() 56 | .map(SmartDirtiesPostDiscoveryFilter::getTestClass) 57 | .collect(Collectors.toSet()); 58 | if (uniqueClasses.size() == 1) { 59 | // This filter is executed several times during discover and execute phases and 60 | // it's not possible to distinguish them here. Sometimes per single test is sent as argument, 61 | // sometimes - the whole suite. If it's a suite more than 1, we can save it and never update. 62 | // If it's 1 - we should also distinguish single test execution. 63 | if (SmartDirtiesTestsSupport.classOrderStateMapSize(engine) <= 1) { 64 | Class testClass = getTestClass(childrenToReorder.get(0)); 65 | SmartDirtiesTestsSupport.setTestClassesLists(engine, TestSortResult.singletonList(testClass)); 66 | } 67 | 68 | // the logic here may differ for JUnit 4 via Maven vs IntelliJ: 69 | // Maven calls this filter several times (first per each test, then with all tests) 70 | return FilterResult.included("Skipping single element"); 71 | } 72 | 73 | childrenToReorder.forEach(testDescriptor::removeChild); 74 | 75 | SmartDirtiesTestsSorter sorter = SmartDirtiesTestsSorter.getInstance(); 76 | TestSortResult testSortResult; 77 | try { 78 | testSortResult = sorter.sort(childrenToReorder, 79 | TestClassExtractor.ofClass(SmartDirtiesPostDiscoveryFilter::getTestClass)); 80 | } catch (Throwable e) { 81 | SmartDirtiesTestsSupport.setFailureCause(e); 82 | throw e; 83 | } 84 | 85 | childrenToReorder.forEach(testDescriptor::addChild); 86 | 87 | SmartDirtiesTestsSupport.setTestClassesLists(engine, testSortResult); 88 | 89 | return FilterResult.included("sorted"); 90 | } 91 | 92 | @NonNull 93 | private static Class getTestClass(TestDescriptor testDescriptor) { 94 | Class testClass = getTestClassOrNull(testDescriptor); 95 | if (testClass == null) { 96 | throw new UnsupportedOperationException("Unsupported TestDescriptor type " + testDescriptor.getClass() 97 | + ", failed to obtain test class"); 98 | } 99 | return testClass; 100 | } 101 | 102 | @Nullable 103 | private static Class getTestClassOrNull(TestDescriptor testDescriptor) { 104 | TestSource testSource = testDescriptor.getSource().orElse(null); 105 | if (testSource instanceof ClassSource) { 106 | ClassSource classSource = (ClassSource) testSource; 107 | return classSource.getJavaClass(); 108 | } 109 | 110 | return null; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/SpringContextEventLoggerListener.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import java.util.Locale; 4 | import java.util.concurrent.TimeUnit; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.context.ApplicationListener; 8 | import org.springframework.context.event.ApplicationContextEvent; 9 | import org.springframework.context.event.ContextClosedEvent; 10 | import org.springframework.context.event.ContextRefreshedEvent; 11 | 12 | /** 13 | * Helper bean that logs spring bootstrap and shutdown events. 14 | * 15 | * @author Sergey Chernov 16 | * @see SmartDirtiesContextTestExecutionListener 17 | */ 18 | public class SpringContextEventLoggerListener implements ApplicationListener { 19 | 20 | private static final Logger logger = LoggerFactory.getLogger(SpringContextEventLoggerListener.class); 21 | 22 | private final long createdNanos = System.nanoTime(); 23 | 24 | public SpringContextEventLoggerListener() { 25 | onCreated(); 26 | } 27 | 28 | protected void onCreated() { 29 | String currentTestClassName = CurrentTestContext.getCurrentTestClassName(); 30 | if (currentTestClassName == null) { 31 | logger.error("Could not resolve current class name, ensure that SmartDirtiesContextTestExecutionListener " + 32 | "is registered for test class. Hint:\n" + 33 | "Maybe you should set @TestExecutionListeners.mergeMode to MERGE_WITH_DEFAULTS for your test class."); 34 | } else { 35 | logger.info("Creating context for {}", currentTestClassName); 36 | } 37 | } 38 | 39 | @Override 40 | public void onApplicationEvent(ApplicationContextEvent event) { 41 | if (event instanceof ContextRefreshedEvent) { 42 | onContextRefreshedEvent((ContextRefreshedEvent) event); 43 | } else if (event instanceof ContextClosedEvent) { 44 | onContextClosedEvent((ContextClosedEvent) event); 45 | } 46 | } 47 | 48 | protected void onContextRefreshedEvent(ContextRefreshedEvent event) { 49 | long nowNanos = System.nanoTime(); 50 | String contextInitFormatted = formatNanos(nowNanos - createdNanos); 51 | boolean isChild = event.getApplicationContext().getParent() != null; 52 | logger.info("Created {} in {} for {}", 53 | isChild ? "child context" : "context", contextInitFormatted, CurrentTestContext.getCurrentTestClassName()); 54 | } 55 | 56 | protected void onContextClosedEvent(ContextClosedEvent event) { 57 | String testClassIdentifier = CurrentTestContext.getCurrentTestClassName(); 58 | boolean isChild = event.getApplicationContext().getParent() != null; 59 | if (testClassIdentifier == null) { 60 | // system shutdown hook 61 | logger.info("Destroying {} (hook)", isChild ? "child context" : "context"); 62 | } else { 63 | // triggered via SmartDirtiesContextTestExecutionListener or spring DirtiesContextTestExecutionListener 64 | logger.info("Destroying {} for {}", isChild ? "child context" : "context", testClassIdentifier); 65 | } 66 | } 67 | 68 | static String formatNanos(long timeNanos) { 69 | long millis = TimeUnit.NANOSECONDS.toMillis(timeNanos); 70 | return String.format(Locale.ROOT, "%.3f", millis / 1000.0d) + "s"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/SpringContextEventLoggerListenerCustomizerFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | import java.util.ServiceLoader; 6 | import org.springframework.context.ConfigurableApplicationContext; 7 | import org.springframework.lang.NonNull; 8 | import org.springframework.test.context.ContextConfigurationAttributes; 9 | import org.springframework.test.context.ContextCustomizer; 10 | import org.springframework.test.context.ContextCustomizerFactory; 11 | import org.springframework.test.context.MergedContextConfiguration; 12 | 13 | /** 14 | * @author Sergey Chernov 15 | */ 16 | public class SpringContextEventLoggerListenerCustomizerFactory implements ContextCustomizerFactory { 17 | 18 | @Override 19 | public ContextCustomizer createContextCustomizer( 20 | @NonNull Class testClass, 21 | @NonNull List configAttributes 22 | ) { 23 | return new ContextCustomizerImpl(); 24 | } 25 | 26 | private static class ContextCustomizerImpl implements ContextCustomizer { 27 | 28 | @Override 29 | public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { 30 | context.addApplicationListener(getSpringContextEventLoggerListener()); 31 | } 32 | 33 | @Override 34 | public boolean equals(Object obj) { 35 | // we need either static singleton ContextCustomizerImpl or equals like this to produce 36 | // equal org.springframework.test.context.MergedContextConfiguration 37 | return getClass() == obj.getClass(); 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | return 0; 43 | } 44 | } 45 | 46 | private static SpringContextEventLoggerListener getSpringContextEventLoggerListener() { 47 | // overridden logic in demo-test-kit 48 | ServiceLoader loader = ServiceLoader.load(SpringContextEventLoggerListener.class, 49 | SpringContextEventLoggerListenerCustomizerFactory.class.getClassLoader()); 50 | 51 | Iterator iterator = loader.iterator(); 52 | if (iterator.hasNext()) { 53 | return iterator.next(); 54 | } else { 55 | return new SpringContextEventLoggerListener(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/TestClassExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import java.util.function.Function; 4 | 5 | /** 6 | * @author Sergey Chernov 7 | */ 8 | public abstract class TestClassExtractor { 9 | 10 | private final ItemType itemType; 11 | 12 | protected TestClassExtractor(ItemType itemType) { 13 | this.itemType = itemType; 14 | } 15 | 16 | public ItemType getItemType() { 17 | return itemType; 18 | } 19 | 20 | public static TestClassExtractor ofClass(Function> testClassExtractor) { 21 | return new TestClassExtractor(ItemType.TEST_CLASS) { 22 | @Override 23 | public Class getTestClass(T test) { 24 | return testClassExtractor.apply(test); 25 | } 26 | }; 27 | } 28 | 29 | public static TestClassExtractor ofMethod(Function> testClassExtractor) { 30 | return new TestClassExtractor(ItemType.TEST_METHOD) { 31 | @Override 32 | public Class getTestClass(T test) { 33 | return testClassExtractor.apply(test); 34 | } 35 | }; 36 | } 37 | 38 | public abstract Class getTestClass(T test); 39 | 40 | public enum ItemType { 41 | TEST_CLASS, 42 | TEST_METHOD 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/TestSortResult.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import static java.util.Collections.emptySet; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | /** 10 | * @author Sergey Chernov 11 | */ 12 | public class TestSortResult { 13 | 14 | private final List>> sortedConfigToTests; 15 | private final Set> nonItClasses; 16 | 17 | TestSortResult(List>> sortedConfigToTests, Set> nonItClasses) { 18 | this.sortedConfigToTests = sortedConfigToTests; 19 | this.nonItClasses = nonItClasses; 20 | } 21 | 22 | public static TestSortResult singletonList(Class testClass) { 23 | // This single test is either integration or not, it will be checked only in case if it's integration. 24 | // So we can skip IntegrationTestFilter 25 | return new TestSortResult(Collections.singletonList(Collections.singletonList(testClass)), emptySet()); 26 | } 27 | 28 | public List>> getSortedConfigToTests() { 29 | return sortedConfigToTests; 30 | } 31 | 32 | public Set> getNonItClasses() { 33 | return nonItClasses; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "TestSortResult{" + 39 | "sortedConfigToTests=" + sortedConfigToTests + 40 | ", nonItClasses=" + nonItClasses + 41 | '}'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/jdbc/CloseableDelegatingDataSource.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.jdbc; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import javax.sql.DataSource; 6 | import org.springframework.jdbc.datasource.DelegatingDataSource; 7 | 8 | /** 9 | * Closeable DataSource which delegates close call to target 10 | * 11 | * @author Sergey Chernov 12 | */ 13 | public class CloseableDelegatingDataSource extends DelegatingDataSource implements Closeable { 14 | 15 | public CloseableDelegatingDataSource() { 16 | // for lazy initialization 17 | } 18 | 19 | public CloseableDelegatingDataSource(DataSource targetDataSource) { 20 | // eagerly initialized DataSource should be closeable 21 | super(requireCloseable(targetDataSource)); 22 | } 23 | 24 | private static DataSource requireCloseable(DataSource targetDataSource) { 25 | if (targetDataSource == null) { 26 | throw new IllegalArgumentException("targetDataSource is null"); 27 | } 28 | if (!(targetDataSource instanceof Closeable)) { 29 | throw new IllegalArgumentException("targetDataSource is not closeable"); 30 | } 31 | return targetDataSource; 32 | } 33 | 34 | @Override 35 | public void close() throws IOException { 36 | DataSource targetDataSource = getTargetDataSource(); 37 | // condition may be false for lazily initialized DataSource 38 | if (targetDataSource instanceof Closeable) { 39 | ((Closeable) targetDataSource).close(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/jdbc/GuavaSuppliers.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.jdbc; 2 | 3 | import java.util.Objects; 4 | import java.util.function.Supplier; 5 | import org.springframework.lang.Nullable; 6 | 7 | /** 8 | * Original code: 9 | * Guava 10 | * Suppliers.java 11 | */ 12 | final class GuavaSuppliers { 13 | 14 | /** 15 | * Returns a supplier which caches the instance retrieved during the first call to {@code get()} 16 | * and returns that value on subsequent calls to {@code get()}. See: memoization 18 | * 19 | *

The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at 20 | * most once unless the underlying {@code get()} throws an exception. 21 | * 22 | *

When the underlying delegate throws an exception then this memoizing supplier will keep 23 | * delegating calls until it returns valid data. 24 | * 25 | *

If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is 26 | * returned directly. 27 | */ 28 | static Supplier memoize(Supplier delegate) { 29 | if (delegate instanceof NonSerializableMemoizingSupplier) { 30 | return delegate; 31 | } 32 | return new NonSerializableMemoizingSupplier<>(delegate); 33 | } 34 | 35 | static class NonSerializableMemoizingSupplier implements Supplier { 36 | volatile Supplier delegate; 37 | volatile boolean initialized; 38 | // "value" does not need to be volatile; visibility piggy-backs 39 | // on volatile read of "initialized". 40 | @Nullable 41 | T value; 42 | 43 | NonSerializableMemoizingSupplier(Supplier delegate) { 44 | this.delegate = Objects.requireNonNull(delegate); 45 | } 46 | 47 | @Override 48 | public T get() { 49 | // A 2-field variant of Double Checked Locking. 50 | if (!initialized) { 51 | synchronized (this) { 52 | if (!initialized) { 53 | T t = delegate.get(); 54 | value = t; 55 | initialized = true; 56 | // Release the delegate to GC. 57 | delegate = null; 58 | return t; 59 | } 60 | } 61 | } 62 | return value; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | Supplier delegate = this.delegate; 68 | return "Suppliers.memoize(" 69 | + (delegate == null ? "" : delegate) 70 | + ")"; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/jdbc/LateInitDataSource.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.jdbc; 2 | 3 | import java.util.function.Supplier; 4 | import javax.sql.DataSource; 5 | import org.springframework.lang.Nullable; 6 | 7 | /** 8 | * DataSource decorator which resolves delegate DataSource on first demand. 9 | *

10 | * This can be an optimization for DataSources created beyond TestContainers-managed Docker containers: the container is 11 | * only started if needed. 12 | *

13 | * Usage example: 14 | *

{@code
15 |  *    @Bean
16 |  *    public DataSource dataSource(PostgreSQLContainer container) {
17 |  *         // lazy late initialization - the JDBC url is not known yet, because container is not running
18 |  *         return new LateInitDataSource(() -> {
19 |  *             LOGGER.info("Late initialization data source docker container {}", container);
20 |  *             // start only on demand
21 |  *             container.start();
22 |  *             return createHikariDataSourceForContainer(container);
23 |  *         });
24 |  *     }
25 |  * }
26 | * This DataSource delegates close call to the target. 27 | * 28 | * @author Sergey Chernov 29 | */ 30 | public class LateInitDataSource extends CloseableDelegatingDataSource { 31 | 32 | @Nullable 33 | private final String name; 34 | private final Supplier dataSourceSupplier; 35 | 36 | public LateInitDataSource(Supplier dataSourceSupplier) { 37 | this(null, dataSourceSupplier); 38 | } 39 | 40 | public LateInitDataSource(String name, Supplier dataSourceSupplier) { 41 | this.name = name; 42 | this.dataSourceSupplier = GuavaSuppliers.memoize(() -> { 43 | DataSource dataSource = dataSourceSupplier.get(); 44 | setTargetDataSource(dataSource); 45 | return dataSource; 46 | }); 47 | } 48 | 49 | @Override 50 | public void afterPropertiesSet() { 51 | // no op to skip getTargetDataSource setup 52 | } 53 | 54 | @Override 55 | protected DataSource obtainTargetDataSource() { 56 | return dataSourceSupplier.get(); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "LateInitDataSource{" + 62 | (name == null ? "" : "name='" + name + '\'') + 63 | ", delegate=" + getTargetDataSource() + 64 | '}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/junit4/AbstractJUnit4SpringIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.junit4; 2 | 3 | import com.github.seregamorph.testsmartcontext.SmartDirtiesContextTestExecutionListener; 4 | import org.springframework.test.context.TestExecutionListeners; 5 | import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; 6 | 7 | /** 8 | * Base class for JUnit 4 integration tests that create spring context. Supports 9 | * {@link SmartDirtiesContextTestExecutionListener} semantics to optimize IT suite execution. 10 | * 11 | * @author Sergey Chernov 12 | * @see SmartDirtiesContextTestExecutionListener 13 | * @deprecated support of JUnit 4 will be removed in 1.0 release 14 | */ 15 | @Deprecated 16 | @TestExecutionListeners(listeners = { 17 | SmartDirtiesContextTestExecutionListener.class, 18 | }, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 19 | public abstract class AbstractJUnit4SpringIntegrationTest extends AbstractJUnit4SpringContextTests { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/jupiter/AbstractJUnitSpringIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.jupiter; 2 | 3 | import com.github.seregamorph.testsmartcontext.SmartDirtiesContextTestExecutionListener; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.test.context.TestExecutionListeners; 6 | import org.springframework.test.context.junit.jupiter.SpringExtension; 7 | 8 | /** 9 | * Base class for JUnit 5 Jupiter integration tests that create spring context. Supports 10 | * {@link SmartDirtiesContextTestExecutionListener} semantics to optimize IT suite execution. 11 | * 12 | * @author Sergey Chernov 13 | * @see SmartDirtiesContextTestExecutionListener 14 | */ 15 | @TestExecutionListeners(listeners = { 16 | SmartDirtiesContextTestExecutionListener.class, 17 | }, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 18 | @ExtendWith(SpringExtension.class) 19 | public abstract class AbstractJUnitSpringIntegrationTest { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/jupiter/SmartDirtiesClassOrderer.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.jupiter; 2 | 3 | import com.github.seregamorph.testsmartcontext.SmartDirtiesTestsSorter; 4 | import com.github.seregamorph.testsmartcontext.SmartDirtiesTestsSupport; 5 | import com.github.seregamorph.testsmartcontext.TestClassExtractor; 6 | import com.github.seregamorph.testsmartcontext.TestSortResult; 7 | import java.util.LinkedHashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | import org.junit.jupiter.api.ClassDescriptor; 11 | import org.junit.jupiter.api.ClassOrderer; 12 | import org.junit.jupiter.api.ClassOrdererContext; 13 | import org.junit.jupiter.api.Nested; 14 | import org.springframework.core.annotation.AnnotationUtils; 15 | 16 | /** 17 | * Auto-discovered Jupiter {@link ClassOrderer} which reorders and groups the integration test classes per their 18 | * configuration. Also stores information about last integration class per configuration, which is used by 19 | * {@link com.github.seregamorph.testsmartcontext.SmartDirtiesContextTestExecutionListener}. 20 | *

21 | * For TestNG test classes - see {@link com.github.seregamorph.testsmartcontext.testng.SmartDirtiesSuiteListener}, for 22 | * JUnit 4 test classes - see {@link com.github.seregamorph.testsmartcontext.SmartDirtiesPostDiscoveryFilter}. 23 | * 24 | * @author Sergey Chernov 25 | */ 26 | public class SmartDirtiesClassOrderer extends SmartDirtiesTestsSupport implements ClassOrderer { 27 | 28 | @Override 29 | public void orderClasses(ClassOrdererContext context) { 30 | List classDescriptors = context.getClassDescriptors(); 31 | if (classDescriptors.isEmpty()) { 32 | return; 33 | } 34 | 35 | // Special notes: Maven has different behavior in comparison with IDEA and Gradle, it calls orderClasses method 36 | // for each test class with a single element of classDescriptors list. That's why we need to handle single-size 37 | // list separately. 38 | 39 | // If Jupiter Test class has @Nested inner classes, for each of them (if there is only one inner class) 40 | // orderClasses will be called 41 | 42 | Set> uniqueClasses = new LinkedHashSet<>(); 43 | for (ClassDescriptor classDescriptor : classDescriptors) { 44 | Class testClass = classDescriptor.getTestClass(); 45 | if (isInnerClass(testClass)) { 46 | if (!uniqueClasses.isEmpty()) { 47 | // this should not happen, they should be never mixed in one call 48 | throw new IllegalStateException("Unexpected mix of inner " + testClass + " and " + uniqueClasses); 49 | } 50 | Nested nested = AnnotationUtils.getAnnotation(testClass, Nested.class); 51 | if (nested == null) { 52 | // this should not happen 53 | throw new IllegalStateException("Missing @Nested annotation for inner " + testClass); 54 | } 55 | // implementation notice: if the exception is thrown from this block, it does not break the 56 | // test execution as it's suppressed in 57 | // org.junit.jupiter.engine.discovery.AbstractOrderingVisitor.doWithMatchingDescriptor 58 | // So this validation will be repeated at BeforeClass 59 | verifyInnerClass(testClass); 60 | } else { 61 | // regular test class 62 | uniqueClasses.add(testClass); 63 | } 64 | } 65 | 66 | if (uniqueClasses.isEmpty()) { 67 | // All are internal (@Nested), we do not reorder them. 68 | // The enclosing classes are already in the SmartDirtiesTestsHolder from previous call 69 | if (SmartDirtiesTestsSupport.classOrderStateMapSize(ENGINE_JUNIT_JUPITER) == 0) { 70 | throw new IllegalStateException("orderClasses is called with inner classes list " + classDescriptors 71 | + " before being called with enclosing class list"); 72 | } 73 | return; 74 | } 75 | 76 | if (uniqueClasses.size() == 1) { 77 | // This filter is executed several times during discover and execute phases and 78 | // it's not possible to distinguish them here. Sometimes per single test is sent as argument, 79 | // sometimes - the whole suite. If it's a suite more than 1, we can save it and never update. 80 | // If it's 1 - we should also distinguish single test execution. 81 | if (SmartDirtiesTestsSupport.classOrderStateMapSize(ENGINE_JUNIT_JUPITER) <= 1) { 82 | Class testClass = classDescriptors.get(0).getTestClass(); 83 | SmartDirtiesTestsSupport.setTestClassesLists(ENGINE_JUNIT_JUPITER, 84 | TestSortResult.singletonList(testClass)); 85 | } 86 | return; 87 | } 88 | 89 | SmartDirtiesTestsSorter sorter = SmartDirtiesTestsSorter.getInstance(); 90 | TestSortResult testClassesLists; 91 | try { 92 | testClassesLists = sorter.sort(classDescriptors, TestClassExtractor.ofClass(ClassDescriptor::getTestClass)); 93 | } catch (Throwable e) { 94 | SmartDirtiesTestsSupport.setFailureCause(e); 95 | throw e; 96 | } 97 | SmartDirtiesTestsSupport.setTestClassesLists(ENGINE_JUNIT_JUPITER, testClassesLists); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/testng/AbstractTestNGSpringIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.testng; 2 | 3 | import com.github.seregamorph.testsmartcontext.SmartDirtiesContextTestExecutionListener; 4 | import org.springframework.test.context.TestExecutionListeners; 5 | import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; 6 | 7 | /** 8 | * Base class for TestNG integration tests that create spring context. Supports 9 | * {@link SmartDirtiesContextTestExecutionListener} semantics to optimize IT suite execution. 10 | * 11 | * @author Sergey Chernov 12 | * @see SmartDirtiesContextTestExecutionListener 13 | */ 14 | @TestExecutionListeners(listeners = { 15 | SmartDirtiesContextTestExecutionListener.class, 16 | }, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 17 | public abstract class AbstractTestNGSpringIntegrationTest extends AbstractTestNGSpringContextTests { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/com/github/seregamorph/testsmartcontext/testng/SmartDirtiesSuiteListener.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext.testng; 2 | 3 | import com.github.seregamorph.testsmartcontext.SmartDirtiesContextTestExecutionListener; 4 | import com.github.seregamorph.testsmartcontext.SmartDirtiesTestsSorter; 5 | import com.github.seregamorph.testsmartcontext.SmartDirtiesTestsSupport; 6 | import com.github.seregamorph.testsmartcontext.TestClassExtractor; 7 | import com.github.seregamorph.testsmartcontext.TestSortResult; 8 | import java.util.LinkedHashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | import org.testng.IAlterSuiteListener; 12 | import org.testng.IMethodInstance; 13 | import org.testng.IMethodInterceptor; 14 | import org.testng.ITestContext; 15 | import org.testng.internal.RuntimeBehavior; 16 | import org.testng.xml.XmlSuite; 17 | 18 | /** 19 | * See description in {@link SmartDirtiesContextTestExecutionListener}. 20 | *

21 | * Reorders TestNG test classes in suite grouping ITs with the same context configuration to minimize number of parallel 22 | * existing contexts. 23 | *

24 | * For Jupiter test classes - see {@link com.github.seregamorph.testsmartcontext.jupiter.SmartDirtiesClassOrderer}, for 25 | * JUnit 4 test classes - see {@link com.github.seregamorph.testsmartcontext.SmartDirtiesPostDiscoveryFilter}. 26 | * 27 | * @author Sergey Chernov 28 | */ 29 | @SuppressWarnings("CodeBlock2Expr") 30 | public class SmartDirtiesSuiteListener extends SmartDirtiesTestsSupport 31 | implements IAlterSuiteListener, IMethodInterceptor { 32 | 33 | @Override 34 | public void alter(List suites) { 35 | // dryRun is only true when called via junit5 testng-engine on discovery phase, there will be subsequent 36 | // call of this method with dryRun=false on execution phase 37 | if (RuntimeBehavior.isDryRun()) { 38 | // the list of test classes is wrong, listener is called per each class as single in suite 39 | return; 40 | } 41 | // TestNG needs it (otherwise reorders back to default alphabetical order) 42 | suites.forEach(suite -> { 43 | suite.getTests().forEach(xmlTest -> { 44 | xmlTest.setPreserveOrder(false); 45 | }); 46 | }); 47 | } 48 | 49 | @Override 50 | public List intercept(List methods, ITestContext context) { 51 | // dryRun is only true when called via junit5 testng-engine on discovery phase, there will be subsequent 52 | // call of this method with dryRun=false on execution phase 53 | if (RuntimeBehavior.isDryRun()) { 54 | // the list of test classes is wrong, listener is called per each class as single in suite 55 | return methods; 56 | } 57 | 58 | Set> uniqueClasses = new LinkedHashSet<>(); 59 | for (IMethodInstance method : methods) { 60 | Class testClass = getTestClass(method); 61 | uniqueClasses.add(testClass); 62 | } 63 | 64 | if (uniqueClasses.size() == 1) { 65 | Class testClass = getTestClass(methods.get(0)); 66 | SmartDirtiesTestsSupport.setTestClassesLists(ENGINE_TESTNG, TestSortResult.singletonList(testClass)); 67 | return methods; 68 | } 69 | 70 | // this intercept method is executed by TestNG before running the suite (both IDEA and maven) 71 | SmartDirtiesTestsSorter sorter = SmartDirtiesTestsSorter.getInstance(); 72 | // Do not store the failure as if it throws, TestNG will fail the suite 73 | // (both pure TestNG and JUnit 5 testng-engine) 74 | TestSortResult testClassesLists = sorter.sort(methods, 75 | TestClassExtractor.ofMethod(SmartDirtiesSuiteListener::getTestClass)); 76 | 77 | SmartDirtiesTestsSupport.setTestClassesLists(ENGINE_TESTNG, testClassesLists); 78 | 79 | return methods; 80 | } 81 | 82 | private static Class getTestClass(IMethodInstance methodInstance) { 83 | return methodInstance.getMethod().getTestClass().getRealClass(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/java/org/springframework/test/context/BootstrapUtilsHelper.java: -------------------------------------------------------------------------------- 1 | package org.springframework.test.context; 2 | 3 | /** 4 | * Accessor of spring-boot package visible utility 5 | * 6 | * @author Sergey Chernov 7 | */ 8 | public final class BootstrapUtilsHelper { 9 | 10 | public static TestContextBootstrapper resolveTestContextBootstrapper(Class testClass) { 11 | // this utility becomes public since spring 6, but for spring 5 we call it from package-private accessor 12 | return BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.createBootstrapContext(testClass)); 13 | } 14 | 15 | private BootstrapUtilsHelper() { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/resources/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter: -------------------------------------------------------------------------------- 1 | com.github.seregamorph.testsmartcontext.SmartDirtiesPostDiscoveryFilter 2 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/resources/META-INF/services/org.testng.ITestNGListener: -------------------------------------------------------------------------------- 1 | com.github.seregamorph.testsmartcontext.testng.SmartDirtiesSuiteListener 2 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.test.context.TestExecutionListener = \ 2 | com.github.seregamorph.testsmartcontext.SmartDirtiesContextTestExecutionListener 3 | 4 | org.springframework.test.context.ContextCustomizerFactory = \ 5 | com.github.seregamorph.testsmartcontext.SpringContextEventLoggerListenerCustomizerFactory 6 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/main/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.testclass.order.default = com.github.seregamorph.testsmartcontext.jupiter.SmartDirtiesClassOrderer 2 | -------------------------------------------------------------------------------- /spring-test-smart-context/src/test/java/com/github/seregamorph/testsmartcontext/SpringContextEventLoggerListenerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.seregamorph.testsmartcontext; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | class SpringContextEventLoggerListenerTest { 7 | 8 | @Test 9 | public void shouldFormatNanos() { 10 | Assertions.assertEquals("120.000s", 11 | SpringContextEventLoggerListener.formatNanos(120000000000L)); 12 | } 13 | } 14 | --------------------------------------------------------------------------------