├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── pom.xml └── src ├── main └── java │ └── org │ └── codefx │ └── demo │ └── junit5 │ ├── Benchmark.java │ ├── BenchmarkExtension.java │ ├── CollectExceptionExtension.java │ ├── CollectExceptions.java │ ├── DisabledByFormula.java │ ├── DisabledCondition.java │ ├── DisabledIfTestFailedWith.java │ ├── DisabledIfTestFailedWithCondition.java │ ├── DisabledOnOs.java │ ├── ExpectedExceptionExtension.java │ ├── IntegrationTest.java │ ├── LambdaTest.java │ ├── OS.java │ ├── OsCondition.java │ ├── RandomIntegerResolver.java │ ├── RandomResolver.java │ ├── SimpleBenchmark.java │ ├── SimpleBenchmarkExtension.java │ ├── Test.java │ ├── TestExceptOnOs.java │ ├── TimeoutExtension.java │ └── scenario │ ├── ScenarioTest.java │ ├── Step.java │ └── StepMethodOrderer.java └── test ├── java └── org │ └── codefx │ └── demo │ ├── junit4 │ ├── JUnit4RuleInJupiter.java │ └── LegacyTest.java │ └── junit5 │ ├── HelloWorldTest.java │ ├── basics │ ├── AssertTest.java │ ├── AssumeTest.java │ ├── DisableTest.java │ ├── LifecycleTest.java │ ├── NamingTest.java │ └── TagTest.java │ ├── dynamic │ ├── ArithmeticNode.java │ ├── ArithmeticTreeTest.java │ ├── ArithmeticTreeTestData.java │ ├── DynamicContainerTest.java │ ├── DynamicTestTest.java │ ├── LambdaTestTest.java │ └── PointTest.java │ ├── extensions │ ├── BenchmarkTest.java │ ├── DisabledByFormulaTest.java │ ├── DisabledIfFailsTest.java │ ├── DisabledOnOsTest.java │ ├── ExpectedExceptionTest.java │ ├── Integration.java │ ├── RandomParameterExtensionTest.java │ ├── SimpleBenchmarkTest.java │ └── TimeoutTest.java │ ├── injection │ ├── CustomInjectionInVariousTests.java │ └── OrderTests.java │ ├── integrations │ ├── MockitoTest.java │ └── PioneerTest.java │ ├── interfaces │ ├── Implementation.java │ └── Interface.java │ ├── nested │ ├── NestTest.java │ └── StackTest.java │ ├── parameterized │ ├── ArgumentAggregatorTest.java │ ├── ArgumentConverterTest.java │ ├── ArgumentSourcesTest.java │ ├── CustomArgumentConverterTest.java │ ├── CustomArgumentsSourceTest.java │ ├── HelloParams.java │ ├── MetaAnnotationTest.java │ └── NameTest.java │ ├── scenario │ ├── ClassTestInstanceTest.java │ └── ScenarioExtensionTests.java │ └── templates │ └── RepeatedInvocationTest.java └── resources ├── junit-platform.properties └── word-lengths.csv /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | # compilation folders 15 | /bin/ 16 | /out/ 17 | /target/ 18 | /build/ 19 | 20 | # Eclipse files and folders 21 | # /.settings keep the settings file for warnings and formatter 22 | .classpath 23 | .project 24 | 25 | # IntelliJ folders 26 | /.idea 27 | /.idea-files 28 | *.iml 29 | 30 | # Gradle folder 31 | /.gradle/ 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demo of JUnit 5 2 | 3 | Demo project accompanying [a series of posts exploring JUnit 5](https://blog.codefx.org/tag/junit-5/). 4 | 5 | ## First Steps 6 | 7 | * [Setup](http://blog.codefx.org/libraries/junit-5-setup/): have a look at [`pom.xml`](pom.xml) or [`build.gradle`](build.gradle) 8 | * [Basics](http://blog.codefx.org/libraries/junit-5-basics/): [`LifecycleTest`](src/test/java/org/codefx/demo/junit5/basics/LifecycleTest.java) is a good introduction, for more details see the other classes in [`test/.../basics`](src/test/java/org/codefx/demo/junit5/basics) 9 | * Tests in interfaces in [`test/.../interfaces`](src/test/java/org/codefx/demo/junit5/interfaces) 10 | 11 | ## Next Steps 12 | 13 | * Parameter injection: demonstrated in [`test/.../injection`](src/test/java/org/codefx/demo/junit5/injection) 14 | * Nested tests: demonstrated in [`test/.../nested`](src/test/java/org/codefx/demo/junit5/nested) 15 | * [Parameterized tests](http://blog.codefx.org/libraries/junit-5-parameterized-tests/): demonstrated in [`test/.../parameterized`](src/test/java/org/codefx/demo/junit5/parameterized), starting with [`HelloParams`](src/test/java/org/codefx/demo/junit5/parameterized/HelloParams.java) 16 | * [Dynamic tests](http://blog.codefx.org/libraries/junit-5-dynamic-tests/): demonstrated in [`test/.../dynamic`](src/test/java/org/codefx/demo/junit5/dynamic) 17 | 18 | ## JUnit 4 and 5 19 | 20 | * [Architecture](http://blog.codefx.org/design/architecture/junit-5-architecture/) (has no code samples) 21 | * Side by side with JUnit 4: configured in [`pom.xml`](pom.xml) and [`build.gradle`](build.gradle) (search for _4.12_) and demonstrated in [`LegacyTest`](src/test/java/org/codefx/demo/junit4/LegacyTest.java) 22 | * JUnit 4 rules in JUnit Jupiter: demonstrated in [JUnit4RuleInJupiter](src/test/java/org/codefx/demo/junit4/JUnit4RuleInJupiter.java) 23 | 24 | ## Extensions 25 | 26 | * [Extension model](http://blog.codefx.org/design/architecture/junit-5-extension-model/): implemented in [`main`](src/main/java/org/codefx/demo/junit5) and used in [`test/.../extensions`](src/test/java/org/codefx/demo/junit5/extensions) 27 | * [Conditions](http://blog.codefx.org/libraries/junit-5-conditions/): implemented in [`main`](src/main/java/org/codefx/demo/junit5) and used in [`test/.../extensions`](src/test/java/org/codefx/demo/junit5/extensions) 28 | * Example integrations, e.g. with Mockito, in [`test/.../integrations`](src/test/java/org/codefx/demo/junit5/integrations) 29 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | sourceCompatibility = 11 6 | targetCompatibility = 11 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | // for writing tests "testCompile" would suffice, but extensions are 14 | // defined in the project's "main" folder, so we need "compile" 15 | compile "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" 16 | testCompile "org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}" 17 | testRuntime "org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}" 18 | 19 | testCompile "junit:junit:4.12" 20 | testCompile "org.junit.jupiter:junit-jupiter-migrationsupport:${junitJupiterVersion}" 21 | testRuntime "org.junit.vintage:junit-vintage-engine:${junitJupiterVersion}" 22 | 23 | testCompile('org.assertj:assertj-core:3.10.0') 24 | testCompile('org.mockito:mockito-core:2.19.1') 25 | testCompile('org.junit-pioneer:junit-pioneer:0.1.2') 26 | testCompile('org.mockito:mockito-junit-jupiter:2.19.1') 27 | } 28 | 29 | test { 30 | useJUnitPlatform { 31 | excludeTags 'database' 32 | } 33 | // systemProperties = [ 34 | // 'junit.jupiter.conditions.deactivate': '*' 35 | // ] 36 | testLogging { 37 | events "passed", "skipped", "failed" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | junitJupiterVersion=5.6.2 2 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | org.codefx.demo 7 | junit-5 8 | 1.0-SNAPSHOT 9 | 10 | 11 | 5.6.2 12 | 11 13 | 11 14 | UTF-8 15 | 16 | 17 | 18 | 19 | 20 | org.junit.jupiter 21 | junit-jupiter-api 22 | ${junit-jupiter-version} 23 | 25 | compile 26 | 27 | 28 | 29 | org.junit.jupiter 30 | junit-jupiter-params 31 | ${junit-jupiter-version} 32 | test 33 | 34 | 35 | 36 | 37 | org.junit.jupiter 38 | junit-jupiter-engine 39 | ${junit-jupiter-version} 40 | test 41 | 42 | 43 | 45 | 46 | junit 47 | junit 48 | 4.13.1 49 | test 50 | 51 | 52 | org.junit.jupiter 53 | junit-jupiter-migrationsupport 54 | ${junit-jupiter-version} 55 | test 56 | 57 | 58 | org.junit.vintage 59 | junit-vintage-engine 60 | ${junit-jupiter-version} 61 | 62 | 63 | 64 | 65 | 66 | org.assertj 67 | assertj-core 68 | 3.10.0 69 | test 70 | 71 | 72 | 73 | org.mockito 74 | mockito-core 75 | 2.19.1 76 | test 77 | 78 | 79 | 80 | org.mockito 81 | mockito-junit-jupiter 82 | 2.19.1 83 | test 84 | 85 | 86 | 87 | org.junit-pioneer 88 | junit-pioneer 89 | 0.1.2 90 | test 91 | 92 | 93 | 94 | 95 | 96 | 97 | maven-compiler-plugin 98 | 3.7.0 99 | 100 | 101 | maven-surefire-plugin 102 | 2.22.1 103 | 104 | database 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/Benchmark.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.ElementType.TYPE; 12 | 13 | /** 14 | * Benchmarks tests and test classes by printing the run time to the console. 15 | *

16 | * If applied to a test, it will report the run time of the individual test without before and after behavior. 17 | * If applied to a class, it will report the cumulated run time of all tests therein. 18 | */ 19 | @Target({ TYPE, METHOD, ANNOTATION_TYPE }) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @ExtendWith(BenchmarkExtension.class) 22 | public @interface Benchmark { } 23 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/BenchmarkExtension.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.AfterAllCallback; 4 | import org.junit.jupiter.api.extension.AfterTestExecutionCallback; 5 | import org.junit.jupiter.api.extension.BeforeAllCallback; 6 | import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; 7 | import org.junit.jupiter.api.extension.ExtensionContext; 8 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace; 9 | 10 | import static java.lang.System.currentTimeMillis; 11 | import static java.util.Collections.singletonMap; 12 | import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; 13 | 14 | class BenchmarkExtension 15 | implements BeforeAllCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, AfterAllCallback { 16 | 17 | private static final Namespace NAMESPACE = Namespace.create("org", "codefx", "BenchmarkExtension"); 18 | 19 | // EXTENSION POINTS 20 | 21 | @Override 22 | public void beforeAll(ExtensionContext context) { 23 | if (!shouldBeBenchmarked(context)) 24 | return; 25 | 26 | storeNowAsLaunchTime(context, LaunchTimeKey.CLASS); 27 | } 28 | 29 | @Override 30 | public void beforeTestExecution(ExtensionContext context) { 31 | if (!shouldBeBenchmarked(context)) 32 | return; 33 | 34 | storeNowAsLaunchTime(context, LaunchTimeKey.TEST); 35 | } 36 | 37 | @Override 38 | public void afterTestExecution(ExtensionContext context) { 39 | if (!shouldBeBenchmarked(context)) 40 | return; 41 | 42 | long launchTime = loadLaunchTime(context, LaunchTimeKey.TEST); 43 | long elapsedTime = currentTimeMillis() - launchTime; 44 | report("Test", context, elapsedTime); 45 | } 46 | 47 | @Override 48 | public void afterAll(ExtensionContext context) { 49 | if (!shouldBeBenchmarked(context)) 50 | return; 51 | 52 | long launchTime = loadLaunchTime(context, LaunchTimeKey.CLASS); 53 | long elapsedTime = currentTimeMillis() - launchTime; 54 | report("Test container", context, elapsedTime); 55 | } 56 | 57 | // HELPER 58 | 59 | private static boolean shouldBeBenchmarked(ExtensionContext context) { 60 | return context.getElement() 61 | .map(el -> isAnnotated(el, Benchmark.class)) 62 | .orElse(false); 63 | } 64 | 65 | private static void storeNowAsLaunchTime(ExtensionContext context, LaunchTimeKey key) { 66 | context.getStore(NAMESPACE).put(key, currentTimeMillis()); 67 | } 68 | 69 | private static long loadLaunchTime(ExtensionContext context, LaunchTimeKey key) { 70 | return context.getStore(NAMESPACE).get(key, long.class); 71 | } 72 | 73 | private static void report(String unit, ExtensionContext context, long elapsedTime) { 74 | String message = String.format("%s '%s' took %d ms.", unit, context.getDisplayName(), elapsedTime); 75 | context.publishReportEntry("Benchmark", message); 76 | } 77 | 78 | private enum LaunchTimeKey { 79 | CLASS, TEST 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/CollectExceptionExtension.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtensionContext; 4 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace; 5 | import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; 6 | 7 | import java.util.HashSet; 8 | import java.util.Optional; 9 | import java.util.Set; 10 | import java.util.stream.Stream; 11 | 12 | public class CollectExceptionExtension implements TestExecutionExceptionHandler { 13 | 14 | private static final Namespace NAMESPACE = Namespace.create("org", "codefx", "CollectExceptions"); 15 | private static final String THROWN_EXCEPTIONS_KEY = "THROWN_EXCEPTIONS_KEY"; 16 | 17 | public static Stream getThrownExceptions(ExtensionContext context) { 18 | return getThrown(context).stream(); 19 | } 20 | 21 | @SuppressWarnings("unchecked") 22 | private static Set getThrown(ExtensionContext context) { 23 | return (Set) context 24 | .getStore(NAMESPACE) 25 | .getOrComputeIfAbsent(THROWN_EXCEPTIONS_KEY, __ -> new HashSet<>()); 26 | } 27 | 28 | @Override 29 | public void handleTestExecutionException(ExtensionContext context, Throwable throwable) 30 | throws Throwable { 31 | if (throwable instanceof Exception) { 32 | Exception exception = (Exception) throwable; 33 | getThrown(context).add(exception); 34 | } 35 | 36 | throw throwable; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/CollectExceptions.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @ExtendWith(CollectExceptionExtension.class) 10 | public @interface CollectExceptions { } 11 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/DisabledByFormula.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ConditionEvaluationResult; 4 | import org.junit.jupiter.api.extension.ExecutionCondition; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | 7 | import java.util.function.BooleanSupplier; 8 | 9 | public class DisabledByFormula implements ExecutionCondition { 10 | 11 | private final BooleanFormula formula; 12 | private final String message; 13 | 14 | private DisabledByFormula(String message, BooleanFormula formula) { 15 | this.formula = formula; 16 | this.message = message; 17 | } 18 | 19 | public static DisabledByFormula disabledWhen(String message, BooleanFormula formula) { 20 | return new DisabledByFormula(message, formula); 21 | } 22 | 23 | public static DisabledByFormula disabledWhen(String message, boolean value) { 24 | return new DisabledByFormula(message, () -> value); 25 | } 26 | 27 | @Override 28 | public ConditionEvaluationResult evaluateExecutionCondition( 29 | ExtensionContext context) { 30 | return formula.evaluate() 31 | // disable when formula is true 32 | ? ConditionEvaluationResult.disabled(message) 33 | : ConditionEvaluationResult.enabled("Not '" + message + "'"); 34 | } 35 | 36 | @FunctionalInterface 37 | public interface BooleanFormula extends BooleanSupplier { 38 | 39 | @Override 40 | @Deprecated 41 | default boolean getAsBoolean() { 42 | return evaluate(); 43 | } 44 | 45 | boolean evaluate(); 46 | 47 | static BooleanFormula from(boolean value) { 48 | return () -> value; 49 | } 50 | 51 | static BooleanFormula from(BooleanSupplier term) { 52 | return term::getAsBoolean; 53 | } 54 | 55 | static BooleanFormula not(boolean value) { 56 | return () -> value; 57 | } 58 | 59 | static BooleanFormula not(BooleanSupplier term) { 60 | return () -> !term.getAsBoolean(); 61 | } 62 | 63 | default BooleanFormula and(BooleanSupplier otherTerm) { 64 | return () -> this.evaluate() && otherTerm.getAsBoolean(); 65 | } 66 | 67 | default BooleanFormula and(boolean value) { 68 | return () -> this.evaluate() && value; 69 | } 70 | 71 | default BooleanFormula or(BooleanSupplier otherTerm) { 72 | return () -> this.evaluate() || otherTerm.getAsBoolean(); 73 | } 74 | 75 | default BooleanFormula or(boolean value) { 76 | return () -> this.evaluate() || value; 77 | } 78 | 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/DisabledCondition.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.extension.ConditionEvaluationResult; 5 | import org.junit.jupiter.api.extension.ExecutionCondition; 6 | import org.junit.jupiter.api.extension.ExtensionContext; 7 | 8 | import java.lang.reflect.AnnotatedElement; 9 | import java.util.Optional; 10 | 11 | import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; 12 | 13 | /** 14 | * An exemplary reimplementation of the extension supporting the {@link Disabled @Disabled} annotation. 15 | */ 16 | public class DisabledCondition implements ExecutionCondition { 17 | 18 | private static final ConditionEvaluationResult ENABLED = 19 | ConditionEvaluationResult.enabled("@Disabled is not present"); 20 | 21 | @Override 22 | public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { 23 | return evaluateIfAnnotated(context.getElement()); 24 | } 25 | 26 | private ConditionEvaluationResult evaluateIfAnnotated(Optional element) { 27 | return element.filter(el -> isAnnotated(el, Disabled.class)) 28 | .map(el -> ConditionEvaluationResult.disabled(el + " is @Disabled")) 29 | .orElse(ENABLED); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/DisabledIfTestFailedWith.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @ExtendWith(CollectExceptionExtension.class) 13 | @ExtendWith(DisabledIfTestFailedWithCondition.class) 14 | public @interface DisabledIfTestFailedWith { 15 | 16 | Class[] value(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/DisabledIfTestFailedWithCondition.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ConditionEvaluationResult; 4 | import org.junit.jupiter.api.extension.ExecutionCondition; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | 7 | import java.util.Arrays; 8 | 9 | import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; 10 | import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; 11 | import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; 12 | 13 | public class DisabledIfTestFailedWithCondition implements ExecutionCondition { 14 | 15 | @Override 16 | public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { 17 | Class[] exceptionTypes = context.getTestClass() 18 | .flatMap(testClass -> findAnnotation(testClass, DisabledIfTestFailedWith.class)) 19 | .orElseThrow(() -> new IllegalStateException("The extension should not be executed " 20 | + "unless the test class is annotated with @DisabledIfTestFailedWith.")) 21 | .value(); 22 | 23 | return disableIfExceptionWasThrown(context, exceptionTypes); 24 | } 25 | 26 | private ConditionEvaluationResult disableIfExceptionWasThrown( 27 | ExtensionContext context, 28 | Class[] exceptions) { 29 | return Arrays.stream(exceptions) 30 | .filter(ex -> wasThrown(context, ex)) 31 | .findAny() 32 | .map(thrown -> disabled(thrown.getSimpleName() + " was thrown.")) 33 | .orElseGet(() -> enabled("")); 34 | } 35 | 36 | private static boolean wasThrown(ExtensionContext context, Class exception) { 37 | return CollectExceptionExtension.getThrownExceptions(context) 38 | .map(Object::getClass) 39 | .anyMatch(exception::isAssignableFrom); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/DisabledOnOs.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @ExtendWith(OsCondition.class) 10 | public @interface DisabledOnOs { 11 | 12 | OS[] value(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/ExpectedExceptionExtension.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.AfterTestExecutionCallback; 4 | import org.junit.jupiter.api.extension.ExtensionContext; 5 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace; 6 | import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; 7 | import org.opentest4j.AssertionFailedError; 8 | 9 | import java.util.Optional; 10 | 11 | import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; 12 | 13 | public class ExpectedExceptionExtension implements TestExecutionExceptionHandler, AfterTestExecutionCallback { 14 | 15 | /* 16 | * This extension implements the exception handler callback to compare the thrown exception 17 | * to what was expected. The after test callback (which is called later) builds on 18 | * the results of that check and fails the test if an exception was expected but not thrown. 19 | */ 20 | 21 | private static final Namespace NAMESPACE = Namespace.create("org", "codefx", "ExpectedException"); 22 | private static final String KEY = "ExceptionWasT"; 23 | 24 | @Override 25 | public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { 26 | boolean throwableMatchesExpectedException = 27 | expectedException(context) 28 | .filter(expected -> expected.isInstance(throwable)) 29 | .isPresent(); 30 | // in the `afterTestExecution` callback we have to pass or fail the test 31 | // depending on whether the exception was thrown or not; 32 | // to do that we need to register whether the exception was thrown; 33 | // (NOTE that if no exception was thrown, NOTHING is registered) 34 | if (throwableMatchesExpectedException) 35 | storeExceptionStatus(context, EXCEPTION.WAS_THROWN_AS_EXPECTED); 36 | else { 37 | // this extension is not in charge of the throwable, so we need to rethrow; 38 | storeExceptionStatus(context, EXCEPTION.WAS_THROWN_NOT_AS_EXPECTED); 39 | throw throwable; 40 | } 41 | } 42 | 43 | @Override 44 | public void afterTestExecution(ExtensionContext context) throws Exception { 45 | switch(loadExceptionStatus(context)) { 46 | case WAS_NOT_THROWN: 47 | expectedException(context) 48 | .map(expected -> new AssertionFailedError("Expected exception " + expected + " was not thrown.", expected, null)) 49 | .ifPresent(ex -> { throw ex; }); 50 | case WAS_THROWN_AS_EXPECTED: 51 | // the exception was thrown as expected so there is nothing to do 52 | case WAS_THROWN_NOT_AS_EXPECTED: 53 | // an exception was thrown but of the wrong type; 54 | // it was rethrown in `handleTestExecutionException` 55 | // so there is nothing to do here 56 | } 57 | } 58 | 59 | private static Optional> expectedException(ExtensionContext context) { 60 | return context.getElement() 61 | .flatMap(el -> findAnnotation(el, Test.class)) 62 | .map(Test::expected) 63 | .filter(exceptionType -> exceptionType != Test.None.class); 64 | } 65 | 66 | private static void storeExceptionStatus(ExtensionContext context, EXCEPTION thrown) { 67 | context.getStore(NAMESPACE).put(KEY, thrown); 68 | } 69 | 70 | private static EXCEPTION loadExceptionStatus(ExtensionContext context) { 71 | EXCEPTION thrown = context.getStore(NAMESPACE).get(KEY, EXCEPTION.class); 72 | if (thrown == null) 73 | return EXCEPTION.WAS_NOT_THROWN; 74 | else 75 | return thrown; 76 | } 77 | 78 | private enum EXCEPTION { 79 | WAS_NOT_THROWN, 80 | WAS_THROWN_AS_EXPECTED, 81 | WAS_THROWN_NOT_AS_EXPECTED, 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Test 14 | @Benchmark 15 | public @interface IntegrationTest { } 16 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/LambdaTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.DynamicTest; 4 | import org.junit.jupiter.api.TestFactory; 5 | import org.junit.jupiter.api.function.Executable; 6 | 7 | import java.io.Serializable; 8 | import java.lang.invoke.SerializedLambda; 9 | import java.lang.reflect.Method; 10 | import java.lang.reflect.Parameter; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Objects; 14 | 15 | import static java.lang.Character.toUpperCase; 16 | import static java.util.Arrays.stream; 17 | import static java.util.stream.Collectors.joining; 18 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 19 | 20 | public class LambdaTest { 21 | 22 | private final List registeredTests = new ArrayList<>(); 23 | 24 | protected void λ(String displayName, Executable test) { 25 | registeredTests.add(dynamicTest(displayName, test)); 26 | } 27 | 28 | protected void λ(NamedTest test) { 29 | String displayName = test.prettyName(); 30 | registeredTests.add(dynamicTest(displayName, () -> test.execute(displayName))); 31 | } 32 | 33 | @TestFactory 34 | List registeredTests() { 35 | return registeredTests; 36 | } 37 | 38 | @FunctionalInterface 39 | public interface NamedTest extends ParameterNameFinder { 40 | 41 | void execute(String name); 42 | 43 | } 44 | 45 | public interface ParameterNameFinder extends MethodFinder { 46 | 47 | default String name() { 48 | return parameterName(0); 49 | } 50 | 51 | default String prettyName() { 52 | return stream(name().split("_")) 53 | .map(word -> toUpperCase(word.charAt(0)) + word.substring(1)) 54 | .collect(joining(" ")); 55 | } 56 | 57 | } 58 | 59 | public interface MethodFinder extends Serializable { 60 | 61 | // as seen on http://benjiweber.co.uk/blog/2015/08/17/lambda-parameter-names-with-reflection/ 62 | 63 | default SerializedLambda serialized() { 64 | try { 65 | Method replaceMethod = getClass().getDeclaredMethod("writeReplace"); 66 | replaceMethod.setAccessible(true); 67 | return (SerializedLambda) replaceMethod.invoke(this); 68 | } catch (Exception ex) { 69 | throw new RuntimeException(ex); 70 | } 71 | } 72 | 73 | default Class getContainingClass() { 74 | try { 75 | String className = serialized().getImplClass().replaceAll("/", "."); 76 | return Class.forName(className); 77 | } catch (Exception ex) { 78 | throw new RuntimeException(ex); 79 | } 80 | } 81 | 82 | default Method method() { 83 | SerializedLambda lambda = serialized(); 84 | Class containingClass = getContainingClass(); 85 | return stream(containingClass.getDeclaredMethods()) 86 | .filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName())) 87 | .findFirst() 88 | .orElseThrow(UnableToGuessMethodException::new); 89 | } 90 | 91 | default Parameter parameter(int n) { 92 | return method().getParameters()[n]; 93 | } 94 | 95 | default String parameterName(int n) { 96 | return checkParameterNamesEnabled(parameter(n).getName()); 97 | } 98 | 99 | default String checkParameterNamesEnabled(String name) { 100 | if ("arg0".equals(name)) { 101 | throw new IllegalStateException( 102 | "You need to compile with javac (at least 8u60 but before 9)" 103 | + " with the option -parameters for parameter reflection to work."); 104 | } 105 | return name; 106 | } 107 | 108 | class UnableToGuessMethodException extends RuntimeException { 109 | 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/OS.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | public enum OS { 4 | 5 | /* 6 | * This class was written for demonstration purposes. 7 | * It is not at all fit for production! 8 | */ 9 | 10 | NIX, 11 | MAC, 12 | WINDOWS; 13 | 14 | public static OS determine() { 15 | String os = System.getProperty("os.name").toLowerCase(); 16 | 17 | if (isWindows(os)) { 18 | return WINDOWS; 19 | } else if (isMac(os)) { 20 | return MAC; 21 | } else if (isUnix(os)) { 22 | return NIX; 23 | } else { 24 | throw new IllegalArgumentException("Unknown OS \"" + os + "\"."); 25 | } 26 | } 27 | 28 | private static boolean isWindows(String os) { 29 | return os.contains("win"); 30 | } 31 | 32 | private static boolean isMac(String os) { 33 | return os.contains("mac"); 34 | } 35 | 36 | private static boolean isUnix(String os) { 37 | return os.contains("nix") || os.contains("nux"); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/OsCondition.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ConditionEvaluationResult; 4 | import org.junit.jupiter.api.extension.ExecutionCondition; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | 7 | import java.lang.reflect.AnnotatedElement; 8 | import java.util.Arrays; 9 | import java.util.Optional; 10 | 11 | import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; 12 | 13 | public class OsCondition implements ExecutionCondition { 14 | 15 | @Override 16 | public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { 17 | return evaluateIfAnnotated(context.getElement()); 18 | } 19 | 20 | private ConditionEvaluationResult evaluateIfAnnotated(Optional element) { 21 | Optional disabled = element.flatMap(el -> findAnnotation(el, DisabledOnOs.class)); 22 | if (disabled.isPresent()) 23 | return disabledIfOn(disabled.get().value()); 24 | 25 | Optional testExcept = element.flatMap(el -> findAnnotation(el, TestExceptOnOs.class)); 26 | if (testExcept.isPresent()) 27 | return disabledIfOn(testExcept.get().value()); 28 | 29 | return ConditionEvaluationResult.enabled(""); 30 | } 31 | 32 | private ConditionEvaluationResult disabledIfOn(OS[] disabledOnOs) { 33 | OS os = OS.determine(); 34 | if (Arrays.asList(disabledOnOs).contains(os)) 35 | return ConditionEvaluationResult.disabled("Test is disabled on " + os + "."); 36 | else 37 | return ConditionEvaluationResult.enabled("Test is not disabled in " + os + "."); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/RandomIntegerResolver.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtensionContext; 4 | import org.junit.jupiter.api.extension.ParameterContext; 5 | import org.junit.jupiter.api.extension.ParameterResolutionException; 6 | import org.junit.jupiter.api.extension.ParameterResolver; 7 | 8 | import java.util.Random; 9 | 10 | public class RandomIntegerResolver implements ParameterResolver { 11 | 12 | private static final Random RANDOM = new Random(); 13 | 14 | @Override 15 | public boolean supportsParameter( 16 | ParameterContext parameterContext, ExtensionContext extensionContext) 17 | throws ParameterResolutionException { 18 | // don't blindly support a common type like `Integer` 19 | // instead it should be annotated with `@Randomized` or something 20 | Class targetType = parameterContext.getParameter().getType(); 21 | return targetType == Integer.class || targetType == int.class; 22 | } 23 | 24 | @Override 25 | public Object resolveParameter( 26 | ParameterContext parameterContext, ExtensionContext extensionContext) 27 | throws ParameterResolutionException { 28 | return RANDOM.nextInt(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/RandomResolver.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtensionContext; 4 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace; 5 | import org.junit.jupiter.api.extension.ParameterContext; 6 | import org.junit.jupiter.api.extension.ParameterResolutionException; 7 | import org.junit.jupiter.api.extension.ParameterResolver; 8 | import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; 9 | 10 | import java.util.Map; 11 | import java.util.Optional; 12 | import java.util.Random; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | public class RandomResolver implements ParameterResolver, TestExecutionExceptionHandler { 16 | 17 | private static final Namespace NAMESPACE = Namespace.create("org", "codefx", "RandomResolver"); 18 | 19 | @Override 20 | public boolean supportsParameter( 21 | ParameterContext parameterContext, ExtensionContext extensionContext) 22 | throws ParameterResolutionException { 23 | // don't blindly support a common type like `Random` 24 | // instead it should be annotated with `@Randomized` or something 25 | Class targetType = parameterContext.getParameter().getType(); 26 | return targetType == Random.class || targetType == SeededRandom.class; 27 | } 28 | 29 | @Override 30 | public Object resolveParameter( 31 | ParameterContext parameterContext, ExtensionContext context) 32 | throws ParameterResolutionException { 33 | return randomByUniqueId(context) 34 | .computeIfAbsent(context.getUniqueId(), SeededRandom::create); 35 | } 36 | 37 | @SuppressWarnings("unchecked") 38 | private static Map randomByUniqueId(ExtensionContext context) { 39 | return context 40 | .getStore(NAMESPACE) 41 | .getOrComputeIfAbsent("Map", key -> new ConcurrentHashMap<>(), Map.class); 42 | } 43 | 44 | @Override 45 | public void handleTestExecutionException( 46 | ExtensionContext context, Throwable throwable) throws Throwable { 47 | String seed = Optional.ofNullable(randomByUniqueId(context).get(context.getUniqueId())) 48 | .map(SeededRandom::seed) 49 | .map(s -> "seed " + s) 50 | .orElse("unknown seed"); 51 | System.out.println("Exception occurred in test based on " + seed); 52 | throw throwable; 53 | } 54 | 55 | public static class SeededRandom extends Random { 56 | 57 | private static Random seeder = new Random(); 58 | 59 | private final String testId; 60 | private final long seed; 61 | 62 | private SeededRandom(String testId, long seed) { 63 | super(seed); 64 | this.testId = testId; 65 | this.seed = seed; 66 | } 67 | 68 | static SeededRandom create(String testId) { 69 | return new SeededRandom(testId, seeder.nextLong()); 70 | } 71 | 72 | public String testId() { 73 | return testId; 74 | } 75 | 76 | public long seed() { 77 | return seed; 78 | } 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/SimpleBenchmark.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.ElementType.TYPE; 12 | 13 | /** 14 | * Benchmarks individual tests by printing the run time to the console. 15 | *

16 | * If applied to a container, all tests therein will be benchmarked individually. 17 | */ 18 | @Target({ TYPE, METHOD, ANNOTATION_TYPE }) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | @ExtendWith(SimpleBenchmarkExtension.class) 21 | public @interface SimpleBenchmark { } 22 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/SimpleBenchmarkExtension.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.AfterTestExecutionCallback; 4 | import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace; 7 | 8 | import static java.lang.System.currentTimeMillis; 9 | import static java.util.Collections.singletonMap; 10 | 11 | class SimpleBenchmarkExtension 12 | implements BeforeTestExecutionCallback, AfterTestExecutionCallback { 13 | 14 | private static final Namespace NAMESPACE = Namespace.create("org", "codefx", "SimpleBenchmarkExtension"); 15 | private static final String LAUNCH_TIME_KEY = "LaunchTime"; 16 | 17 | @Override 18 | public void beforeTestExecution(ExtensionContext context) { 19 | storeNowAsLaunchTime(context); 20 | } 21 | 22 | @Override 23 | public void afterTestExecution(ExtensionContext context) { 24 | long launchTime = loadLaunchTime(context); 25 | long elapsedTime = currentTimeMillis() - launchTime; 26 | report(context, elapsedTime); 27 | } 28 | 29 | private static void storeNowAsLaunchTime(ExtensionContext context) { 30 | context.getStore(NAMESPACE).put(LAUNCH_TIME_KEY, currentTimeMillis()); 31 | } 32 | 33 | private static long loadLaunchTime(ExtensionContext context) { 34 | return context.getStore(NAMESPACE).get(LAUNCH_TIME_KEY, long.class); 35 | } 36 | 37 | private static void report(ExtensionContext context, long elapsedTime) { 38 | String message = String.format("Test '%s' took %d ms.", context.getDisplayName(), elapsedTime); 39 | context.publishReportEntry("Benchmark", message); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/Test.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | 11 | @Target(METHOD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @ExtendWith(ExpectedExceptionExtension.class) 14 | @ExtendWith(TimeoutExtension.class) 15 | @org.junit.jupiter.api.Test 16 | public @interface Test { 17 | 18 | class None extends Throwable { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | private None() { 23 | } 24 | } 25 | 26 | /** 27 | * Optionally specify expected, a Throwable, to cause a test method to succeed if 28 | * and only if an exception of the specified class is thrown by the method. 29 | */ 30 | Class expected() default None.class; 31 | 32 | /** 33 | * Optionally specify timeout in milliseconds to cause a test method to fail if it 34 | * takes longer than that number of milliseconds. 35 | *

36 | * NOTE: Unlike the same parameter on JUnit 4's {@code @Test} annotation, this one 37 | * does not lead to a long running test being abandoned. 38 | * Tests will always be allowed to finish (if they do that at all) and their run time might lead 39 | * to the test being failed retroactively. 40 | *

41 | */ 42 | long timeout() default 0L; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/TestExceptOnOs.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @ExtendWith(OsCondition.class) 11 | @Test 12 | public @interface TestExceptOnOs { 13 | 14 | OS[] value(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/TimeoutExtension.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.extension.AfterTestExecutionCallback; 4 | import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace; 7 | import org.opentest4j.AssertionFailedError; 8 | 9 | import java.util.Optional; 10 | 11 | import static java.lang.String.format; 12 | import static java.lang.System.currentTimeMillis; 13 | import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; 14 | 15 | class TimeoutExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { 16 | 17 | private static final Namespace NAMESPACE = Namespace.create("org", "codefx", "Timeout"); 18 | private static final String LAUNCH_TIME_KEY = "LaunchTime"; 19 | 20 | @Override 21 | public void beforeTestExecution(ExtensionContext context) { 22 | storeNowAsLaunchTime(context); 23 | } 24 | 25 | @Override 26 | public void afterTestExecution(ExtensionContext context) { 27 | annotatedTimeout(context).ifPresent(timeout -> failTestIfRanTooLong(context, timeout)); 28 | } 29 | 30 | private void failTestIfRanTooLong(ExtensionContext context, Long timeout) { 31 | long launchTime = loadLaunchTime(context); 32 | long elapsedTime = currentTimeMillis() - launchTime; 33 | 34 | if (elapsedTime > timeout) { 35 | String message = format( 36 | "Test '%s' was supposed to run no longer than %d ms but ran %d ms.", 37 | context.getDisplayName(), timeout, elapsedTime); 38 | throw new AssertionFailedError(message, timeout, elapsedTime); 39 | } 40 | } 41 | 42 | private Optional annotatedTimeout(ExtensionContext context) { 43 | return context.getElement() 44 | .flatMap(el -> findAnnotation(el, Test.class)) 45 | .map(Test::timeout) 46 | .filter(timeout -> timeout != 0L); 47 | } 48 | 49 | private static void storeNowAsLaunchTime(ExtensionContext context) { 50 | context.getStore(NAMESPACE).put(LAUNCH_TIME_KEY, currentTimeMillis()); 51 | } 52 | 53 | private static long loadLaunchTime(ExtensionContext context) { 54 | return context.getStore(NAMESPACE).get(LAUNCH_TIME_KEY, long.class); 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/scenario/ScenarioTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.scenario; 2 | 3 | import org.junit.jupiter.api.TestInstance; 4 | import org.junit.jupiter.api.TestInstance.Lifecycle; 5 | import org.junit.jupiter.api.TestMethodOrder; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | @Target({ ElementType.TYPE }) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @TestInstance(Lifecycle.PER_CLASS) 16 | @TestMethodOrder(StepMethodOrderer.class) 17 | @ExtendWith(StepMethodOrderer.class) 18 | public @interface ScenarioTest { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/scenario/Step.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.scenario; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ ElementType.METHOD }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Test 13 | public @interface Step { 14 | 15 | String[] after() default { }; 16 | 17 | String[] next() default { }; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/demo/junit5/scenario/StepMethodOrderer.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.scenario; 2 | 3 | import org.junit.jupiter.api.MethodDescriptor; 4 | import org.junit.jupiter.api.MethodOrderer; 5 | import org.junit.jupiter.api.MethodOrdererContext; 6 | import org.junit.jupiter.api.extension.ConditionEvaluationResult; 7 | import org.junit.jupiter.api.extension.ExecutionCondition; 8 | import org.junit.jupiter.api.extension.ExtensionContext; 9 | import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collection; 13 | import java.util.Comparator; 14 | import java.util.HashMap; 15 | import java.util.HashSet; 16 | import java.util.LinkedList; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | import java.util.Queue; 21 | import java.util.Set; 22 | import java.util.stream.Stream; 23 | 24 | import static java.util.Objects.requireNonNull; 25 | import static java.util.function.Predicate.not; 26 | import static java.util.stream.Collectors.toList; 27 | 28 | public class StepMethodOrderer implements MethodOrderer, TestExecutionExceptionHandler, ExecutionCondition { 29 | 30 | // TODO: use extension context to save state 31 | private static Collection executionOrder; 32 | private Collection failedTests = new HashSet<>(); 33 | 34 | @Override 35 | public void orderMethods(MethodOrdererContext context) { 36 | executionOrder = directedTests(context); 37 | List orderedTests = topologicalSort(executionOrder).stream() 38 | .map(DirectedNode::testMethod) 39 | .collect(toList()); 40 | context.getMethodDescriptors().sort(Comparator.comparing(orderedTests::indexOf)); 41 | } 42 | 43 | // TODO: consider parallel execution with `getDefaultExecutionMode` 44 | 45 | // TODO: find a better name for `DirectedNode` and this method 46 | private Collection directedTests(MethodOrdererContext context) { 47 | Map tests = new HashMap<>(); 48 | Map> nextEdges = new HashMap<>(); 49 | 50 | context 51 | .getMethodDescriptors() 52 | .forEach(testMethod -> { 53 | String testName = testMethod.getMethod().getName(); 54 | tests.put(testName, new DirectedNode(testMethod)); 55 | // TODO handle absence of `@Step` 56 | testMethod 57 | .findAnnotation(Step.class).stream() 58 | .flatMap(step -> Stream.of(step.next())) 59 | // TODO: filter seems unnecessary because by default `next` is empty 60 | .filter(not(String::isEmpty)) 61 | .forEach(nextTestName -> nextEdges 62 | .computeIfAbsent(testName, __ -> new HashSet<>()) 63 | .add(nextTestName)); 64 | testMethod 65 | .findAnnotation(Step.class).stream() 66 | .flatMap(step -> Stream.of(step.after())) 67 | // TODO: filter seems unnecessary because by default `after` is empty 68 | .filter(not(String::isEmpty)) 69 | .forEach(beforeTestName -> nextEdges 70 | .computeIfAbsent(beforeTestName, __ -> new HashSet<>()) 71 | .add(testName)); 72 | }); 73 | nextEdges.forEach((testMethodName, nextTestNames) -> { 74 | nextTestNames.stream() 75 | // TODO: handle missing nodes, which come from wrong "next" attribute values 76 | .map(nextTestName -> { 77 | DirectedNode nextTestNode = tests.get(nextTestName); 78 | if (nextTestNode == null) 79 | throw new IllegalArgumentException("There is no test with name '" + nextTestName + "'."); 80 | else 81 | return nextTestNode; 82 | }) 83 | .forEach(nextTestNode -> tests.get(testMethodName).children().add(nextTestNode)); 84 | }); 85 | 86 | return tests.values(); 87 | } 88 | 89 | // TODO: check details 90 | // source: https://gist.github.com/wadejason/36bfe5fb0f119de409492dd7b14d6120 91 | public List topologicalSort(Collection nodes) { 92 | // write your code here 93 | ArrayList order = new ArrayList<>(); 94 | 95 | if (nodes == null) { 96 | return order; 97 | } 98 | 99 | // 1. collect indegree 100 | // Map indegree = new HashMap<>(); 101 | // for (DirectedNode node : nodes) { 102 | // indegree.put(node, 0); 103 | // } 104 | // for (DirectedNode node : nodes) { 105 | // for (DirectedNode neighbor : node.nieghbors) { 106 | // indegree.put(neighbor, map.get(neighbor) + 1); 107 | // } 108 | // } 109 | Map indegree = getIndegree(nodes); 110 | 111 | // 2. put all nodes that indegree = 0 into queue; 112 | Queue queue = new LinkedList<>(); 113 | 114 | for (DirectedNode node : nodes) { 115 | if (indegree.get(node) == 0) { 116 | queue.offer(node); 117 | order.add(node); 118 | } 119 | } 120 | 121 | // 3. Topological Sorting - BFS 122 | while (!queue.isEmpty()) { 123 | DirectedNode node = queue.poll(); 124 | for (DirectedNode neighbor : node.children()) { 125 | // everytime we need to indegree - 1 126 | indegree.put(neighbor, indegree.get(neighbor) - 1); 127 | if (indegree.get(neighbor) == 0) { 128 | queue.offer(neighbor); 129 | order.add(neighbor); 130 | } 131 | } 132 | } 133 | // nodes is cyclic or not 134 | if (order.size() == nodes.size()) { 135 | return order; 136 | } 137 | return null; 138 | } 139 | 140 | private Map getIndegree(Collection graph) { 141 | Map indegree = new HashMap<>(); 142 | for (DirectedNode node : graph) { 143 | indegree.put(node, 0); 144 | } 145 | 146 | for (DirectedNode node : graph) { 147 | for (DirectedNode neighbor : node.children()) { 148 | indegree.put(neighbor, indegree.get(neighbor) + 1); 149 | } 150 | } 151 | return indegree; 152 | } 153 | 154 | @Override 155 | public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { 156 | String testName = context.getRequiredTestMethod().getName(); 157 | DirectedNode testNode = executionOrder.stream() 158 | .filter(node -> node.testMethod().getMethod().getName().equals(testName)) 159 | // TODO: write proper error messages 160 | .reduce((__, ___) -> { 161 | throw new IllegalArgumentException(""); 162 | }) 163 | .orElseThrow(IllegalArgumentException::new); 164 | failedTests.add(testNode); 165 | throw throwable; 166 | } 167 | 168 | @Override 169 | public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { 170 | if (failedTests.isEmpty()) 171 | return ConditionEvaluationResult.enabled(""); 172 | 173 | boolean testIsDescendantOfFailedTest = failedTests.stream() 174 | .flatMap(DirectedNode::descendants) 175 | .anyMatch(node -> Objects.equals( 176 | node.testMethod().getMethod().getName(), 177 | context.getRequiredTestMethod().getName())); 178 | 179 | return testIsDescendantOfFailedTest 180 | ? ConditionEvaluationResult.disabled("An earlier scenario test failed") 181 | : ConditionEvaluationResult.enabled(""); 182 | } 183 | 184 | private static class DirectedNode { 185 | 186 | private final Set children; 187 | private final MethodDescriptor testMethod; 188 | 189 | private DirectedNode(MethodDescriptor testMethod) { 190 | this.testMethod = requireNonNull(testMethod); 191 | this.children = new HashSet<>(); 192 | } 193 | 194 | public Set children() { 195 | return children; 196 | } 197 | 198 | public Stream descendants() { 199 | return Stream.concat( 200 | Stream.of(this), 201 | children.stream().flatMap(DirectedNode::descendants) 202 | ); 203 | } 204 | 205 | public MethodDescriptor testMethod() { 206 | return testMethod; 207 | } 208 | 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit4/JUnit4RuleInJupiter.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit4; 2 | 3 | import org.junit.Rule; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; 6 | import org.junit.rules.ExpectedException; 7 | 8 | import java.util.List; 9 | 10 | @EnableRuleMigrationSupport 11 | class JUnit4RuleInJupiter { 12 | 13 | @Rule 14 | public ExpectedException thrown = ExpectedException.none(); 15 | 16 | @Test 17 | void useExpectedExceptionRule() { 18 | List list = List.of(); 19 | 20 | thrown.expect(IndexOutOfBoundsException.class); 21 | thrown.expectMessage("Index 0 out of bounds for length 0"); 22 | // this call fails 23 | list.get(0); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit4/LegacyTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit4; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import java.lang.StackWalker.StackFrame; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class LegacyTest { 14 | 15 | @Rule 16 | public ExpectedException thrown = ExpectedException.none(); 17 | 18 | @Test 19 | public void testWithJUnit4_failsIfNotRunWithJUnit5Platform() { 20 | Optional vintageFrame = StackWalker 21 | .getInstance() 22 | .walk(frames -> frames 23 | .peek(System.out::println) 24 | .map(StackFrame::getClassName) 25 | .filter(method -> method.contains("vintage")) 26 | .findAny()); 27 | assertThat(vintageFrame).isNotEmpty(); 28 | } 29 | 30 | @Test 31 | public void useExpectedExceptionRule() { 32 | List list = List.of(); 33 | 34 | thrown.expect(IndexOutOfBoundsException.class); 35 | thrown.expectMessage("Index 0 out of bounds for length 0"); 36 | // this call fails 37 | list.get(0); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/HelloWorldTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.when; 10 | 11 | /** 12 | * Typical "Hello World"; also shows that Mockito and AssertJ are compatible. 13 | */ 14 | class HelloWorldTest { 15 | 16 | @Test 17 | void helloJUnit5() { 18 | System.out.println("Hello, JUnit 5."); 19 | } 20 | 21 | @Test 22 | void usingOtherLibs() { 23 | List mockedList = when( 24 | mock(List.class) 25 | .isEmpty()) 26 | .thenReturn(true) 27 | .getMock(); 28 | // passes because we just mocked 'mockedList.isEmpty' to return true 29 | assertThat(mockedList).isEmpty(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/basics/AssertTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.basics; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | import static java.time.Duration.of; 9 | import static java.time.temporal.ChronoUnit.MILLIS; 10 | import static java.util.Arrays.asList; 11 | import static org.junit.jupiter.api.Assertions.assertAll; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertFalse; 14 | import static org.junit.jupiter.api.Assertions.assertNotSame; 15 | import static org.junit.jupiter.api.Assertions.assertThrows; 16 | import static org.junit.jupiter.api.Assertions.assertTimeout; 17 | import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; 18 | import static org.junit.jupiter.api.Assertions.assertTrue; 19 | import static org.junit.jupiter.api.Assertions.fail; 20 | 21 | class AssertTest { 22 | 23 | @Test 24 | void assertWithBoolean() { 25 | assertTrue(true); 26 | assertTrue(this::truism); 27 | 28 | assertFalse(false, () -> "Really " + "expensive " + "message" + "."); 29 | } 30 | 31 | private boolean truism() { 32 | return true; 33 | } 34 | 35 | @Test 36 | void assertWithComparison() { 37 | List expected = asList("element"); 38 | List actual = new LinkedList<>(expected); 39 | 40 | assertEquals(expected, actual); 41 | assertEquals(expected, actual, "Different 'List' implementations should be equal."); 42 | assertEquals(expected, actual, () -> "Different " + "'List' implementations " + "should be equal."); 43 | 44 | assertNotSame(expected, actual, "Obviously not the same instance."); 45 | } 46 | 47 | @Test 48 | void failTheTest_fails() { 49 | fail("epicly"); 50 | } 51 | 52 | @Test 53 | void assertAllProperties_fails() { 54 | Address address = new Address("New City", "Some Street", "No"); 55 | 56 | assertAll("address", 57 | () -> assertEquals("Neustadt", address.city), 58 | () -> assertEquals("Irgendeinestraße", address.street), 59 | () -> assertEquals("Nr", address.number) 60 | ); 61 | } 62 | 63 | static class Address { 64 | 65 | final String city; 66 | final String street; 67 | final String number; 68 | 69 | private Address(String city, String street, String number) { 70 | this.city = city; 71 | this.street = street; 72 | this.number = number; 73 | } 74 | } 75 | 76 | @Test 77 | void assertExceptions() { 78 | Exception exception = assertThrows(Exception.class, this::throwing); 79 | assertEquals("I'm failing on purpose.", exception.getMessage()); 80 | } 81 | 82 | private void throwing() { 83 | throw new IndexOutOfBoundsException("I'm failing on purpose."); 84 | } 85 | 86 | @Test 87 | void assertTimeout_runsLate_failsButFinishes() { 88 | assertTimeout(of(100, MILLIS), () -> { 89 | sleepUninterrupted(250); 90 | // you will see this message 91 | System.out.println("Woke up"); 92 | }); 93 | } 94 | 95 | @Test 96 | void assertTimeoutPreemptively_runsLate_failsAndAborted() { 97 | assertTimeoutPreemptively(of(100, MILLIS), () -> { 98 | sleepUninterrupted(250); 99 | // you will NOT see this message 100 | System.out.println("Woke up"); 101 | }); 102 | } 103 | 104 | private static void sleepUninterrupted(long millis) { 105 | try { 106 | Thread.sleep(millis); 107 | } catch (InterruptedException ex) { } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/basics/AssumeTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.basics; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 6 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 7 | import static org.junit.jupiter.api.Assumptions.assumingThat; 8 | 9 | class AssumeTest { 10 | 11 | @Test 12 | void exitIfFalseIsTrue() { 13 | assumeTrue(false); 14 | System.exit(1); 15 | } 16 | 17 | @Test 18 | void exitIfTrueIsFalse() { 19 | assumeFalse(this::truism); 20 | System.exit(1); 21 | } 22 | 23 | private boolean truism() { 24 | return true; 25 | } 26 | 27 | @Test 28 | void exitIfNullEqualsString() { 29 | assumingThat( 30 | "null".equals(null), 31 | () -> System.exit(1) 32 | ); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/basics/DisableTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.basics; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.condition.DisabledOnJre; 6 | import org.junit.jupiter.api.condition.EnabledOnOs; 7 | import org.junit.jupiter.api.condition.JRE; 8 | import org.junit.jupiter.api.condition.OS; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | class DisableTest { 13 | 14 | @Test 15 | @Disabled("Y U No Pass?!") 16 | void failingTest() { 17 | assertTrue(false); 18 | } 19 | 20 | @Test 21 | @EnabledOnOs(OS.LINUX) 22 | @DisabledOnJre(JRE.JAVA_10) 23 | void conflictingConditions_executed() { 24 | assertTrue(true); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/basics/LifecycleTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.basics; 2 | 3 | import org.codefx.demo.junit5.DisabledOnOs; 4 | import org.codefx.demo.junit5.OS; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Disabled; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | import static org.junit.jupiter.api.Assertions.fail; 15 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 16 | 17 | class LifecycleTest { 18 | 19 | @BeforeAll 20 | static void initializeExternalResources() { 21 | System.out.println("Initializing external resources..."); 22 | } 23 | 24 | @BeforeEach 25 | void initializeMockObjects() { 26 | System.out.println("Initializing mock objects..."); 27 | } 28 | 29 | @Test 30 | void someTest() { 31 | System.out.println("Running some test..."); 32 | assertTrue(true); 33 | } 34 | 35 | @Test 36 | void otherTest() { 37 | assumeTrue(true); 38 | 39 | System.out.println("Running another test..."); 40 | assertNotEquals(1, 42, "Why would these be the same?"); 41 | } 42 | 43 | @Test 44 | @Disabled 45 | void disabledTest() { 46 | System.exit(1); 47 | } 48 | 49 | @Test 50 | @DisabledOnOs(OS.NIX) 51 | void disabledNixTest() { 52 | fail("Only runs on Unix/Linux"); 53 | } 54 | 55 | @AfterEach 56 | void tearDown() { 57 | System.out.println("Tearing down..."); 58 | } 59 | 60 | @AfterAll 61 | static void freeExternalResources() { 62 | System.out.println("Freeing external resources..."); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/basics/NamingTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.basics; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | @DisplayName("What a nice name...") 7 | class NamingTest { 8 | 9 | @Test 10 | @DisplayName("... for a test") 11 | void test() { } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/basics/TagTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.basics; 2 | 3 | import org.junit.jupiter.api.Tag; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class TagTest { 9 | 10 | @Test 11 | @Tag("database") 12 | void database() { 13 | fail("This test should have been filtered and not executed!"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/dynamic/ArithmeticNode.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.dynamic; 2 | 3 | import java.util.List; 4 | import java.util.function.Supplier; 5 | import java.util.function.ToLongFunction; 6 | import java.util.stream.LongStream; 7 | import java.util.stream.Stream; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | 11 | /** 12 | * A simplified version of a node in a tree that adds and multiplies longs. 13 | */ 14 | interface ArithmeticNode { 15 | 16 | long evaluate(); 17 | 18 | List operands(); 19 | 20 | static ArithmeticNode operationFor(ArithmeticOperator operator, ArithmeticNode... operands) { 21 | return new OperationNode(operator, operands); 22 | } 23 | 24 | static ArithmeticNode valueOf(long value) { 25 | return new ValueNode(value); 26 | } 27 | 28 | class OperationNode implements ArithmeticNode { 29 | 30 | private final ArithmeticOperator operator; 31 | 32 | private final ArithmeticNode[] operands; 33 | 34 | private OperationNode(ArithmeticOperator operator, ArithmeticNode[] operands) { 35 | this.operator = requireNonNull(operator); 36 | this.operands = requireNonNull(operands); 37 | } 38 | 39 | @Override 40 | public long evaluate() { 41 | long[] operandValues = Stream.of(operands) 42 | .mapToLong(ArithmeticNode::evaluate) 43 | .toArray(); 44 | return operator.evaluate(operandValues); 45 | } 46 | 47 | @Override 48 | public List operands() { 49 | return List.of(operands); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return operator.toString(); 55 | } 56 | 57 | } 58 | 59 | class ValueNode implements ArithmeticNode { 60 | 61 | private final long value; 62 | 63 | private ValueNode(long value) { 64 | this.value = value; 65 | } 66 | 67 | @Override 68 | public long evaluate() { 69 | return value; 70 | } 71 | 72 | @Override 73 | public List operands() { 74 | return List.of(); 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "Value " + value; 80 | } 81 | 82 | } 83 | 84 | enum ArithmeticOperator { 85 | 86 | MULTIPLY( 87 | operands -> LongStream 88 | .of(operands) 89 | // implementation error to make tests interesting 90 | .map(operand -> operand % 10 == 0 ? operand / 10 : operand) 91 | .reduce(1, (o1, o2) -> o1 * o2), 92 | () -> "Multiplication"), 93 | 94 | ADD( 95 | operands -> LongStream 96 | .of(operands) 97 | // implementation error to make tests interesting 98 | .map(operand -> operand == 4 ? 3 : operand) 99 | .sum(), 100 | () -> "Addition"); 101 | 102 | private final ToLongFunction compute; 103 | private final Supplier toString; 104 | 105 | ArithmeticOperator(ToLongFunction compute, Supplier toString) { 106 | this.compute = compute; 107 | this.toString = toString; 108 | } 109 | 110 | public long evaluate(long... operands) { 111 | return compute.applyAsLong(operands); 112 | } 113 | 114 | public String toString() { 115 | return toString.get(); 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/dynamic/ArithmeticTreeTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.dynamic; 2 | 3 | import org.junit.jupiter.api.DynamicNode; 4 | import org.junit.jupiter.api.DynamicTest; 5 | import org.junit.jupiter.api.TestFactory; 6 | 7 | import java.util.Collection; 8 | import java.util.stream.Stream; 9 | 10 | import static java.util.stream.Stream.concat; 11 | import static java.util.stream.Stream.of; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; 14 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 15 | 16 | class ArithmeticTreeTest { 17 | 18 | @TestFactory 19 | DynamicNode testArithmeticTree() { 20 | return generateTestPlan(ArithmeticTreeTestData.generate()); 21 | } 22 | 23 | @TestFactory 24 | DynamicNode testRandomArithmeticTree() { 25 | return generateTestPlan(ArithmeticTreeTestData.generateRandom()); 26 | } 27 | 28 | /* 29 | * Below you find the generation of a test plan from the arithmetic tree. 30 | */ 31 | 32 | private DynamicNode generateTestPlan(ArithmeticTreeTestData treeTestData) { 33 | return generateTestTreeFor(treeTestData.tree(), treeTestData); 34 | } 35 | 36 | private static DynamicNode generateTestTreeFor( 37 | ArithmeticNode arithmeticNode, ArithmeticTreeTestData treeTestData) { 38 | var testForNode = generateTestFor(arithmeticNode, treeTestData); 39 | if (arithmeticNode.operands().isEmpty()) 40 | return testForNode; 41 | else { 42 | var testsForChildren = generateTestsFor(arithmeticNode.operands(), treeTestData); 43 | var expected = treeTestData.resultFor(arithmeticNode); 44 | var testName = arithmeticNode + " should evaluate to " + expected + " (ops '+3' and '*10' fail)"; 45 | return dynamicContainer(testName, concat(of(testForNode), testsForChildren)); 46 | } 47 | } 48 | 49 | private static DynamicTest generateTestFor( 50 | ArithmeticNode arithmeticNode, ArithmeticTreeTestData treeTestData) { 51 | var expected = treeTestData.resultFor(arithmeticNode); 52 | var testName = arithmeticNode.operands().isEmpty() 53 | ? arithmeticNode + " should evaluate to " + expected 54 | : arithmeticNode + " of operands should evaluate to " + expected; 55 | return dynamicTest(testName, () -> { 56 | var actual = arithmeticNode.evaluate(); 57 | assertThat(actual).isEqualTo(expected); 58 | }); 59 | } 60 | 61 | private static Stream generateTestsFor( 62 | Collection operands, ArithmeticTreeTestData treeTestData) { 63 | return operands.stream() 64 | .map(operand -> generateTestTreeFor(operand, treeTestData)); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/dynamic/ArithmeticTreeTestData.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.dynamic; 2 | 3 | import org.codefx.demo.junit5.dynamic.ArithmeticNode.ArithmeticOperator; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | import java.util.OptionalLong; 11 | import java.util.Random; 12 | import java.util.stream.IntStream; 13 | import java.util.stream.Stream; 14 | 15 | import static org.codefx.demo.junit5.dynamic.ArithmeticNode.ArithmeticOperator.ADD; 16 | import static org.codefx.demo.junit5.dynamic.ArithmeticNode.ArithmeticOperator.MULTIPLY; 17 | import static org.codefx.demo.junit5.dynamic.ArithmeticNode.operationFor; 18 | import static org.codefx.demo.junit5.dynamic.ArithmeticNode.valueOf; 19 | 20 | /** 21 | * A test helper that generates an arithmetic tree together with the correct solutions. 22 | */ 23 | class ArithmeticTreeTestData { 24 | 25 | private final ArithmeticNode tree; 26 | private final Map results; 27 | 28 | private ArithmeticTreeTestData(ArithmeticNode tree, Map results) { 29 | this.tree = tree; 30 | this.results = results; 31 | } 32 | 33 | static ArithmeticTreeTestData generate() { 34 | var _1 = valueOf(1); 35 | var _2 = valueOf(2); 36 | var _3 = valueOf(3); 37 | var _4 = valueOf(4); 38 | var _5 = valueOf(5); 39 | var add_1_3_2 = operationFor(ADD, _1, _3, _2); 40 | var add_2_5 = operationFor(ADD, _2, _5); 41 | var multiply_6_7 = operationFor(MULTIPLY, add_1_3_2, add_2_5); 42 | var add_4_42 = operationFor(ADD, _4, multiply_6_7); 43 | var results = Map.of( 44 | _1, 1L, _2, 2L, _3, 3L, _4, 4L, _5, 5L, 45 | add_1_3_2, 6L, 46 | add_2_5, 7L, 47 | multiply_6_7, 42L, 48 | add_4_42, 46L); 49 | return new ArithmeticTreeTestData(add_4_42, results); 50 | } 51 | 52 | static ArithmeticTreeTestData generateRandom() { 53 | var random = new Random(); 54 | var results = new HashMap(); 55 | 56 | for (int i = 0; i < 5; i++) { 57 | long number = random.nextInt(100); 58 | results.put(valueOf(number), number); 59 | } 60 | 61 | ArithmeticNode topNode = null; 62 | for (int i = 0; i < 5; i++) { 63 | var operatorIndex = random.nextInt(ArithmeticOperator.values().length); 64 | var operator = ArithmeticOperator.values()[operatorIndex]; 65 | var nodes = new ArrayList<>(results.keySet()); 66 | var operands = pickRandomNodes(nodes, random); 67 | var result = determineResult(operator, operands, results); 68 | topNode = operationFor(operator, operands); 69 | results.put(topNode, result); 70 | } 71 | 72 | return new ArithmeticTreeTestData(topNode, results); 73 | } 74 | 75 | private static ArithmeticNode[] pickRandomNodes(List nodes, Random random) { 76 | return IntStream.range(0, random.nextInt(3) + 2) 77 | // compute a random index 78 | .map(n -> random.nextInt(nodes.size())) 79 | .mapToObj(nodes::get) 80 | .toArray(ArithmeticNode[]::new); 81 | } 82 | 83 | private static long determineResult( 84 | ArithmeticOperator operator, 85 | ArithmeticNode[] operands, 86 | HashMap results) { 87 | switch (operator) { 88 | case MULTIPLY: 89 | return Stream.of(operands) 90 | .mapToLong(results::get) 91 | .reduce(1, (o1, o2) -> o1 * o2); 92 | case ADD: 93 | return Stream.of(operands) 94 | .mapToLong(results::get) 95 | .sum(); 96 | default: throw new IllegalStateException(); 97 | } 98 | } 99 | 100 | ArithmeticNode tree() { 101 | return tree; 102 | } 103 | 104 | long resultFor(ArithmeticNode node) { 105 | return Optional.of(results.get(node)).orElseThrow(() -> new IllegalArgumentException("No result for " + node)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/dynamic/DynamicContainerTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.dynamic; 2 | 3 | import org.junit.jupiter.api.DynamicContainer; 4 | import org.junit.jupiter.api.TestFactory; 5 | 6 | import java.util.List; 7 | 8 | import static java.util.Arrays.asList; 9 | import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; 10 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 11 | 12 | class DynamicContainerTest { 13 | 14 | @TestFactory 15 | List registeredTests() { 16 | return asList( 17 | dynamicContainer( 18 | "Dynamic Container #1", 19 | asList( 20 | dynamicTest( 21 | "Dynamic Test #1", 22 | () -> System.out.println("Hi, this is Dynamic Test #1!")), 23 | dynamicTest( 24 | "Dynamic Test #2", 25 | () -> System.out.println("Hi, this is Dynamic Test #2!"))) 26 | ), 27 | dynamicContainer( 28 | "Dynamic Container #2", 29 | asList( 30 | dynamicTest( 31 | "Dynamic Test #A", 32 | () -> System.out.println("Hi, this is Dynamic Test #A!")), 33 | dynamicTest( 34 | "Dynamic Test #B", 35 | () -> System.out.println("Hi, this is Dynamic Test #B!"))) 36 | ) 37 | ); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/dynamic/DynamicTestTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.dynamic; 2 | 3 | import org.junit.jupiter.api.DynamicTest; 4 | import org.junit.jupiter.api.TestFactory; 5 | 6 | import java.util.List; 7 | 8 | import static java.util.Arrays.asList; 9 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 10 | 11 | class DynamicTestTest { 12 | 13 | @TestFactory 14 | List registeredTests() { 15 | return asList( 16 | dynamicTest("Dynamic Test #1", () -> System.out.println("Hi, this is Dynamic Test #1!")), 17 | dynamicTest("Dynamic Test #2", () -> System.out.println("Hi, this is Dynamic Test #2!"))); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/dynamic/LambdaTestTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.dynamic; 2 | 3 | import org.codefx.demo.junit5.LambdaTest; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | class LambdaTestTest extends LambdaTest {{ 8 | 9 | /* 10 | * NOTE: Using the lambda parameter's name as a test name no longer works on Java 9+ 11 | * (see https://bugs.openjdk.java.net/browse/JDK-8138729). 12 | */ 13 | 14 | λ("My first lambda test", () -> { 15 | System.out.println("Hi, this is Lambda Test #1"); 16 | assertTrue(true); 17 | }); 18 | 19 | λ(my_second_lambda_test_fails -> { 20 | System.out.println("Hi, this is Lambda Test #2"); 21 | assertTrue(false); 22 | }); 23 | 24 | }} 25 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/dynamic/PointTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.dynamic; 2 | 3 | import org.junit.jupiter.api.DynamicTest; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.TestFactory; 6 | 7 | import java.util.List; 8 | import java.util.stream.Stream; 9 | 10 | import static java.lang.Math.sqrt; 11 | import static java.util.Arrays.asList; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 14 | import static org.junit.jupiter.api.DynamicTest.stream; 15 | 16 | class PointTest { 17 | 18 | @TestFactory 19 | List testPointsDynamically() { 20 | return List.of( 21 | dynamicTest( 22 | "A Great Test For Point", 23 | () -> { 24 | // test code 25 | }), 26 | dynamicTest( 27 | "Another Great Test For Point", 28 | () -> { 29 | // test code 30 | }) 31 | ); 32 | } 33 | 34 | /* 35 | * NOTE: In essence, these are parameterized tests and outside of a demo 36 | * that feature should be used instead for such tests. 37 | */ 38 | 39 | void testDistanceComputation(Point p1, Point p2, double distance) { 40 | assertEquals(distance, p1.distanceTo(p2)); 41 | } 42 | 43 | @Test 44 | void testDistanceComputations_loop() { 45 | List testData = createTestData(); 46 | for (PointPointDistance datum : testData) { 47 | testDistanceComputation( 48 | datum.point1(), datum.point2(), datum.distance()); 49 | } 50 | } 51 | 52 | @TestFactory 53 | Stream testDistanceComputations_testFactory1() { 54 | List testData = createTestData(); 55 | return testData.stream() 56 | .map(datum -> dynamicTest( 57 | "Testing " + datum, 58 | () -> testDistanceComputation( 59 | datum.point1(), datum.point2(), datum.distance() 60 | ))); 61 | } 62 | 63 | @TestFactory 64 | Stream testDistanceComputations_testFactory2() { 65 | return stream( 66 | createTestData().iterator(), 67 | datum -> "Testing " + datum, 68 | datum -> testDistanceComputation( 69 | datum.point1(), datum.point2(), datum.distance())); 70 | } 71 | 72 | // INNER CLASSES 73 | 74 | private List createTestData() { 75 | return asList( 76 | new PointPointDistance(0, 0, 0, 0, 0), // pass 77 | new PointPointDistance(0, 0, 1, 1, 1), // fail 78 | new PointPointDistance(1, 2, 3, 4, 5), // fail 79 | new PointPointDistance(1, 2, 4, 6, 5) // pass 80 | ); 81 | } 82 | 83 | static class PointPointDistance { 84 | 85 | private final Point p1, p2; 86 | private final double distance; 87 | 88 | PointPointDistance(int x1, int y1, int x2, int y2, double distance) { 89 | this(new Point(x1, y1), new Point(x2, y2), distance); 90 | } 91 | 92 | PointPointDistance(Point p1, Point p2, double distance) { 93 | this.p1 = p1; 94 | this.p2 = p2; 95 | this.distance = distance; 96 | } 97 | 98 | Point point1() { 99 | return p1; 100 | } 101 | 102 | Point point2() { 103 | return p2; 104 | } 105 | 106 | double distance() { 107 | return distance; 108 | } 109 | 110 | @Override 111 | public String toString() { 112 | return "| " + p1 + " - " + p2 + " | = " + distance; 113 | } 114 | } 115 | 116 | static class Point { 117 | 118 | private final int x, y; 119 | 120 | Point(int x, int y) { 121 | this.x = x; 122 | this.y = y; 123 | } 124 | 125 | double distanceTo(Point other) { 126 | return sqrt((x - other.x) * (x - other.x) + (y - other.y) * (y - other.y)); 127 | } 128 | 129 | @Override 130 | public String toString() { 131 | return "(" + x + ", " + y + ")"; 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/BenchmarkTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.Benchmark; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | @Benchmark 9 | class BenchmarkTest { 10 | 11 | @Test 12 | void notBenchmarked() { 13 | assertTrue(true); 14 | } 15 | 16 | @Test 17 | @Benchmark 18 | void benchmarked() throws InterruptedException { 19 | Thread.sleep(100); 20 | assertTrue(true); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/DisabledByFormulaTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.DisabledByFormula; 4 | import org.codefx.demo.junit5.OS; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.RegisterExtension; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | import static java.time.LocalDateTime.now; 11 | import static org.codefx.demo.junit5.DisabledByFormula.disabledWhen; 12 | import static org.junit.jupiter.api.Assertions.fail; 13 | 14 | class DisabledByFormulaTest { 15 | 16 | private static final LocalDateTime MAYAN_B_AK_TUN_13 = LocalDateTime.of(2012, 12, 21, 0, 0); 17 | 18 | @RegisterExtension 19 | static final DisabledByFormula FORMULA = disabledWhen( 20 | "After Mayan b'ak'tun 13 and on Linux", 21 | now().isAfter(MAYAN_B_AK_TUN_13) && OS.determine() == OS.NIX); 22 | 23 | @Test 24 | void doom_failsOnNonUnix() { 25 | fail("DOOM! 😱"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/DisabledIfFailsTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.DisabledIfTestFailedWith; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertFalse; 7 | 8 | @DisabledIfTestFailedWith(RuntimeException.class) 9 | class DisabledIfFailsTest { 10 | 11 | private static boolean ONE_TEST_FAILED = false; 12 | 13 | @Test 14 | void assertNoTestFailed_thenFail_1() { 15 | assertThenFail(); 16 | } 17 | 18 | @Test 19 | void assertNoTestFailed_thenFail_2() { 20 | assertThenFail(); 21 | } 22 | 23 | private void assertThenFail() { 24 | assertFalse(ONE_TEST_FAILED, "No test should run after another failed!"); 25 | ONE_TEST_FAILED = true; 26 | throw new IndexOutOfBoundsException("I'm failing on purpose."); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/DisabledOnOsTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.DisabledOnOs; 4 | import org.codefx.demo.junit5.OS; 5 | import org.codefx.demo.junit5.TestExceptOnOs; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | class DisabledOnOsTest { 11 | 12 | /* 13 | * NOTE: In the meantime, Jupiter created an official version of this condition 14 | * and outside of a demo it should be used instead of this implementation. 15 | */ 16 | 17 | @Test 18 | void runsOnAllOS() { 19 | assertTrue(true); 20 | } 21 | 22 | @Test 23 | @DisabledOnOs(OS.NIX) 24 | void doesNotRunOnLinux_fails() { 25 | assertTrue(false); 26 | } 27 | 28 | @Test 29 | @DisabledOnOs(OS.WINDOWS) 30 | void doesNotRunOnWindows_fails() { 31 | assertTrue(false); 32 | } 33 | 34 | @TestExceptOnOs(OS.NIX) 35 | void doesNotRunOnLinuxEither_fails() { 36 | assertTrue(false); 37 | } 38 | 39 | @TestExceptOnOs(OS.WINDOWS) 40 | void doesNotRunOnWindowsEither_fails() { 41 | assertTrue(false); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/ExpectedExceptionTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.Test; 4 | 5 | class ExpectedExceptionTest { 6 | 7 | @Test 8 | void noExceptionExpected_throwsNoException() { 9 | // do nothing 10 | } 11 | 12 | @Test 13 | void noExceptionExpected_throwsException_fails() { 14 | throw new IndexOutOfBoundsException("I'm failing on purpose."); 15 | } 16 | 17 | @Test(expected = IndexOutOfBoundsException.class) 18 | void exceptionExpected_throwsNoException_fails() { 19 | // do nothing 20 | } 21 | 22 | @Test(expected = IndexOutOfBoundsException.class) 23 | void exceptionExpected_throwsException() { 24 | throw new IndexOutOfBoundsException("I'm failing on purpose."); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/Integration.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.IntegrationTest; 4 | 5 | class Integration { 6 | 7 | @IntegrationTest 8 | void sleep() { 9 | System.out.println("You should see a report entry informing you of the test's run time."); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/RandomParameterExtensionTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.RandomResolver; 4 | import org.codefx.demo.junit5.RandomResolver.SeededRandom; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | 12 | @ExtendWith(RandomResolver.class) 13 | class RandomParameterExtensionTest { 14 | 15 | @BeforeAll 16 | static void beforeAll_1(SeededRandom r) { 17 | System.out.println("Before all #1: " + r.seed()); 18 | } 19 | 20 | @BeforeAll 21 | static void beforeAll_2(SeededRandom r) { 22 | System.out.println("Before all #2: " + r.seed()); 23 | } 24 | 25 | @BeforeEach 26 | void beforeEach_1(SeededRandom r) { 27 | System.out.println("Before each #1: " + r.seed()); 28 | } 29 | 30 | @BeforeEach 31 | void beforeEach_2(SeededRandom r) { 32 | System.out.println("Before each #2: " + r.seed()); 33 | } 34 | 35 | @Test 36 | void test(SeededRandom r) { 37 | System.out.println("Test: " + r.seed()); 38 | } 39 | 40 | @Test 41 | void failingTest(SeededRandom r) { 42 | System.out.println("Failing Test: " + r.seed()); 43 | throw new IndexOutOfBoundsException("I'm failing on purpose."); 44 | } 45 | 46 | @AfterEach 47 | void afterEach_1(SeededRandom r) { 48 | System.out.println("After each #1: " + r.seed()); 49 | } 50 | 51 | @AfterEach 52 | void afterEach_2(SeededRandom r) { 53 | System.out.println("After each #2: " + r.seed()); 54 | } 55 | 56 | @AfterAll 57 | static void afterAll_1(SeededRandom r) { 58 | System.out.println("After all #1: " + r.seed()); 59 | } 60 | 61 | @AfterAll 62 | static void afterAll_2(SeededRandom r) { 63 | System.out.println("After all #2: " + r.seed()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/SimpleBenchmarkTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.SimpleBenchmark; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | @SimpleBenchmark 9 | class SimpleBenchmarkTest { 10 | 11 | @Test 12 | void benchmarked() { 13 | assertTrue(true); 14 | } 15 | 16 | @Test 17 | @SimpleBenchmark 18 | void benchmarkedTwice() throws InterruptedException { 19 | Thread.sleep(100); 20 | assertTrue(true); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/extensions/TimeoutTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.extensions; 2 | 3 | import org.codefx.demo.junit5.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | class TimeoutTest { 8 | 9 | @Test 10 | void quickTestWithoutTimeout() { 11 | assertTrue(true); 12 | } 13 | 14 | @Test(timeout = 0) 15 | void quickTestWithoutZeroTimeout() { 16 | assertTrue(true); 17 | } 18 | 19 | @Test(timeout = 10_000) 20 | void quickTestWithoutVeryLargeTimeout() { 21 | assertTrue(true); 22 | } 23 | 24 | @Test(timeout = 10) 25 | void longRunningTest_fails() throws InterruptedException { 26 | Thread.sleep(100); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/injection/CustomInjectionInVariousTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.injection; 2 | 3 | import org.codefx.demo.junit5.RandomIntegerResolver; 4 | import org.junit.jupiter.api.DynamicTest; 5 | import org.junit.jupiter.api.RepeatedTest; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.TestFactory; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.MethodSource; 11 | 12 | import java.util.stream.Stream; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 16 | 17 | public class CustomInjectionInVariousTests { 18 | 19 | @Test 20 | @ExtendWith(RandomIntegerResolver.class) 21 | void regular(int randomized) { 22 | System.out.println("Random integer: " + randomized); 23 | } 24 | 25 | @TestFactory 26 | @ExtendWith(RandomIntegerResolver.class) 27 | Stream dynamic(int randomized) { 28 | return Stream.of( 29 | dynamicTest("#1", () -> System.out.println("Random integer: " + randomized)), 30 | dynamicTest("#2", () -> System.out.println("Random integer: " + randomized)) 31 | ); 32 | } 33 | 34 | @ParameterizedTest 35 | @MethodSource 36 | @ExtendWith(RandomIntegerResolver.class) 37 | void parameterized(String param, int randomized) { 38 | System.out.println("Random integer: " + randomized); 39 | assertTrue(true); 40 | } 41 | 42 | private static Stream parameterized() { 43 | return Stream.of("first", "second"); 44 | } 45 | 46 | @RepeatedTest(5) 47 | @ExtendWith(RandomIntegerResolver.class) 48 | void repeated(int randomized) { 49 | System.out.println("Random integer: " + randomized); 50 | assertTrue(true); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/injection/OrderTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.injection; 2 | 3 | import org.codefx.demo.junit5.RandomIntegerResolver; 4 | import org.junit.jupiter.api.RepeatedTest; 5 | import org.junit.jupiter.api.RepetitionInfo; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.TestReporter; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | 10 | class OrderTests { 11 | 12 | @Test 13 | @ExtendWith(RandomIntegerResolver.class) 14 | void customParameterFirst(int randomized, TestReporter reporter) { 15 | reporter.publishEntry("first parameter", "" + randomized); 16 | } 17 | 18 | @Test 19 | @ExtendWith(RandomIntegerResolver.class) 20 | void jupiterParameterFirst(TestReporter reporter, int randomized) { 21 | reporter.publishEntry("first parameter", "" + randomized); 22 | } 23 | 24 | @RepeatedTest(3) 25 | @ExtendWith(RandomIntegerResolver.class) 26 | void repetitionInfoFirst(RepetitionInfo info, TestReporter reporter, int randomized) { 27 | reporter.publishEntry("first parameter", "" + randomized); 28 | } 29 | 30 | @RepeatedTest(3) 31 | @ExtendWith(RandomIntegerResolver.class) 32 | void repetitionInfoLast(TestReporter reporter, int randomized, RepetitionInfo info) { 33 | reporter.publishEntry("first parameter", "" + randomized); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/integrations/MockitoTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.integrations; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.jupiter.MockitoExtension; 8 | 9 | import java.awt.Point; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @ExtendWith(MockitoExtension.class) 14 | class MockitoTest { 15 | 16 | @InjectMocks 17 | private Circle circle; 18 | 19 | @Mock 20 | private Point center; 21 | 22 | @Test 23 | void shouldInjectMocks() { 24 | assertThat(center).isNotNull(); 25 | assertThat(circle).isNotNull(); 26 | assertThat(circle.center()).isSameAs(center); 27 | } 28 | 29 | static class Circle { 30 | 31 | private final Point center; 32 | private final double radius; 33 | 34 | Circle(Point center) { 35 | this.center = center; 36 | this.radius = 1.0d; 37 | } 38 | 39 | Point center() { 40 | return center; 41 | } 42 | 43 | double radius() { 44 | return radius; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "Circle{" + 50 | "center=" + center + 51 | '}'; 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/integrations/PioneerTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.integrations; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.TestReporter; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.junitpioneer.jupiter.TempDirectory; 7 | import org.junitpioneer.jupiter.TempDirectory.TempDir; 8 | 9 | import java.nio.file.Path; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | class PioneerTest { 14 | 15 | @Test 16 | @ExtendWith(TempDirectory.class) 17 | void testTempDirInjection(@TempDir Path tempDir, TestReporter reporter) { 18 | assertNotNull(tempDir); 19 | reporter.publishEntry("Temporary directory", tempDir.toString()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/interfaces/Implementation.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.interfaces; 2 | 3 | public class Implementation implements Interface { 4 | 5 | // when run with JUnit Jupiter this class executes the inherited default test methods 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/interfaces/Interface.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.interfaces; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | public interface Interface { 8 | 9 | @Test 10 | default void passes() { 11 | assertTrue(true); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/nested/NestTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.nested; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Nested; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | class NestTest { 11 | 12 | int count = Integer.MIN_VALUE; 13 | 14 | @BeforeEach 15 | void setCountToZero() { 16 | count = 0; 17 | } 18 | 19 | @Test 20 | void countIsZero() { 21 | assertEquals(0, count); 22 | } 23 | 24 | @Nested 25 | class CountGreaterZero { 26 | 27 | @BeforeEach 28 | void increaseCount() { 29 | count++; 30 | } 31 | 32 | @Test 33 | void countIsGreaterZero() { 34 | assertTrue(count > 0); 35 | } 36 | 37 | @Nested 38 | class CountMuchGreaterZero { 39 | 40 | @BeforeEach 41 | void increaseCount() { 42 | count += Integer.MAX_VALUE / 2; 43 | } 44 | 45 | @Test 46 | void countIsLarge() { 47 | assertTrue(count > Integer.MAX_VALUE / 2); 48 | } 49 | 50 | } 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/nested/StackTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.nested; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.EmptyStackException; 9 | import java.util.Stack; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertFalse; 13 | import static org.junit.jupiter.api.Assertions.assertThrows; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | /** 17 | * Demonstration of JUnit 5 features (particularly nesting and naming) that was taken from the 18 | * JUnit 5 User Guide. 19 | */ 20 | @DisplayName("A stack") 21 | class StackTest { 22 | 23 | Stack stack; 24 | 25 | @Test 26 | @DisplayName("is instantiated with new Stack()") 27 | void isInstantiatedWithNew() { 28 | new Stack<>(); 29 | } 30 | 31 | @Nested 32 | @DisplayName("when new") 33 | class WhenNew { 34 | 35 | @BeforeEach 36 | void createNewStack() { 37 | stack = new Stack<>(); 38 | } 39 | 40 | @Test 41 | @DisplayName("is empty") 42 | void isEmpty() { 43 | assertTrue(stack.isEmpty()); 44 | } 45 | 46 | @Test 47 | @DisplayName("throws EmptyStackException when popped") 48 | void throwsExceptionWhenPopped() { 49 | assertThrows(EmptyStackException.class, () -> stack.pop()); 50 | } 51 | 52 | @Test 53 | @DisplayName("throws EmptyStackException when peeked") 54 | void throwsExceptionWhenPeeked() { 55 | assertThrows(EmptyStackException.class, () -> stack.peek()); 56 | } 57 | 58 | @Nested 59 | @DisplayName("after pushing an element") 60 | class AfterPushing { 61 | 62 | String anElement = "an element"; 63 | 64 | @BeforeEach 65 | void pushAnElement() { 66 | stack.push(anElement); 67 | } 68 | 69 | @Test 70 | @DisplayName("it is no longer empty") 71 | void isNotEmpty() { 72 | assertFalse(stack.isEmpty()); 73 | } 74 | 75 | @Test 76 | @DisplayName("returns the element when popped and is empty") 77 | void returnElementWhenPopped() { 78 | assertEquals(anElement, stack.pop()); 79 | assertTrue(stack.isEmpty()); 80 | } 81 | 82 | @Test 83 | @DisplayName("returns the element when peeked but remains not empty") 84 | void returnElementWhenPeeked() { 85 | assertEquals(anElement, stack.peek()); 86 | assertFalse(stack.isEmpty()); 87 | } 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/parameterized/ArgumentAggregatorTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.parameterized; 2 | 3 | import org.codefx.demo.junit5.parameterized.CustomArgumentConverterTest.Point; 4 | import org.junit.jupiter.api.TestReporter; 5 | import org.junit.jupiter.api.extension.ParameterContext; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.aggregator.AggregateWith; 8 | import org.junit.jupiter.params.aggregator.ArgumentsAccessor; 9 | import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; 10 | import org.junit.jupiter.params.aggregator.ArgumentsAggregator; 11 | import org.junit.jupiter.params.converter.ArgumentConversionException; 12 | import org.junit.jupiter.params.converter.ArgumentConverter; 13 | import org.junit.jupiter.params.provider.CsvSource; 14 | 15 | import java.lang.annotation.ElementType; 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | import java.lang.annotation.Target; 19 | 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | import static org.junit.jupiter.api.Assertions.assertEquals; 22 | import static org.junit.jupiter.api.Assertions.assertNotNull; 23 | 24 | class ArgumentAggregatorTest { 25 | 26 | @ParameterizedTest 27 | @CsvSource({ "0, 0, 0", "1, 0, 1", "1, 1, 0", "1.414, 1, 1", "2.236, 2, 1" }) 28 | void testPointNorm(double norm, ArgumentsAccessor arguments) { 29 | Point point = Point.from(arguments.getDouble(1), arguments.getDouble(2)); 30 | assertEquals(norm, point.norm(), 0.01); 31 | } 32 | 33 | @ParameterizedTest 34 | @CsvSource({ "0, 0, 0", "1, 0, 1", "1, 1, 0", "1.414, 1, 1", "2.236, 2, 1" }) 35 | // without ArgumentsAccessor in there, this leads to a ParameterResolutionException 36 | void testEatingArguments(double norm, ArgumentsAccessor arguments, TestReporter reporter) { 37 | reporter.publishEntry("norm", norm + ""); 38 | assertThat(norm).isNotNegative(); 39 | } 40 | 41 | @ParameterizedTest 42 | @CsvSource({ "0, 0, 0", "1, 0, 1", "1, 1, 0", "1.414, 1, 1", "2.236, 2, 1" }) 43 | void testPointNorm(double norm, @AggregatePoint Point point) { 44 | assertEquals(norm, point.norm(), 0.01); 45 | } 46 | 47 | @Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER }) 48 | @Retention(RetentionPolicy.RUNTIME) 49 | @AggregateWith(PointAggregator.class) 50 | @interface AggregatePoint { } 51 | 52 | static class PointAggregator implements ArgumentsAggregator { 53 | 54 | @Override 55 | public Object aggregateArguments( 56 | ArgumentsAccessor arguments, ParameterContext context) throws ArgumentsAggregationException { 57 | return Point.from(arguments.getDouble(1), arguments.getDouble(2)); 58 | } 59 | 60 | } 61 | 62 | @ParameterizedTest 63 | @CsvSource({ "0" }) 64 | void testNoFactoryAccess_errors(ArgumentsAccessor arguments) { 65 | // would be nice to pass NoFactoryConverter... 66 | NoFactory nope = arguments.get(0, NoFactory.class); 67 | assertNotNull(nope); 68 | } 69 | 70 | static class NoFactoryConverter implements ArgumentConverter { 71 | 72 | @Override 73 | public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { 74 | if (source instanceof String) 75 | return new NoFactory(Integer.parseInt((String) source)); 76 | throw new ArgumentConversionException(source + " could not be converted."); 77 | } 78 | } 79 | 80 | static class NoFactory { 81 | 82 | NoFactory(int __) { } 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/parameterized/ArgumentConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.parameterized; 2 | 3 | import org.codefx.demo.junit5.parameterized.CustomArgumentConverterTest.Point; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.CsvSource; 6 | import org.junit.jupiter.params.provider.ValueSource; 7 | 8 | import java.time.LocalDateTime; 9 | import java.time.Year; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | 14 | public class ArgumentConverterTest { 15 | 16 | @ParameterizedTest 17 | @ValueSource(strings = { "x" }) 18 | void testSimpleCharConversion(char c) { 19 | assertNotNull(c); 20 | } 21 | 22 | @ParameterizedTest 23 | @ValueSource(strings = { "☺️" }) 24 | void testUtfCharConversion_errors(char c) { 25 | assertNotNull(c); 26 | } 27 | 28 | @ParameterizedTest 29 | @CsvSource({ "true, 3.14159265359, JUNE, 2017, 2017-06-21T22:00:00" }) 30 | void testDefaultConverters( 31 | boolean b, double d, Summer s, Year y, LocalDateTime dt) { 32 | } 33 | 34 | enum Summer { 35 | JUNE, JULY, AUGUST, SEPTEMBER; 36 | } 37 | 38 | @ParameterizedTest 39 | @ValueSource(strings = { "x️" }) 40 | void testTypeWithOneFactory(TypeWithOneFactory o) { 41 | assertNotNull(o); 42 | } 43 | 44 | static class TypeWithOneFactory { 45 | 46 | static TypeWithOneFactory of(String s) { 47 | return new TypeWithOneFactory(); 48 | } 49 | 50 | } 51 | 52 | @ParameterizedTest 53 | @ValueSource(strings = { "x️" }) 54 | void testTypeWithTwoFactories_constructorCalled(TypeWithTwoFactories o) { 55 | assertNotNull(o); 56 | } 57 | 58 | static class TypeWithTwoFactories { 59 | 60 | TypeWithTwoFactories(String s) { 61 | System.out.println("Constructor called with " + s); 62 | } 63 | 64 | static TypeWithTwoFactories of(String s) { 65 | System.out.println("Factory `of` called with " + s); 66 | return new TypeWithTwoFactories(s); 67 | } 68 | 69 | static TypeWithTwoFactories from(String s) { 70 | System.out.println("Factory `from` called with " + s); 71 | return new TypeWithTwoFactories(s); 72 | } 73 | 74 | } 75 | 76 | @ParameterizedTest 77 | @CsvSource({ "(0/0), 0", "(0/1), 1", "(1/0), 1", "(1/1), 1.414", "(2/1), 2.236" }) 78 | // works because Point::from is suitable factory 79 | void testPointNorm(Point point, double norm) { 80 | assertEquals(norm, point.norm(), 0.01); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/parameterized/ArgumentSourcesTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.parameterized; 2 | 3 | import org.junit.jupiter.api.TestInfo; 4 | import org.junit.jupiter.api.TestReporter; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.Arguments; 7 | import org.junit.jupiter.params.provider.CsvFileSource; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | import org.junit.jupiter.params.provider.EnumSource; 10 | import org.junit.jupiter.params.provider.MethodSource; 11 | import org.junit.jupiter.params.provider.ValueSource; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.stream.Stream; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.junit.jupiter.api.Assertions.assertNotNull; 18 | 19 | public class ArgumentSourcesTest { 20 | 21 | @ParameterizedTest 22 | @ValueSource(longs = { 42, 63 }) 23 | void withValueSourceLong(long number) { 24 | assertNotNull(number); 25 | } 26 | 27 | @ParameterizedTest 28 | @ValueSource(strings = { "Hello", "Parameterized" }) 29 | void withOtherParams(String word, TestInfo info, TestReporter reporter) { 30 | reporter.publishEntry(info.getDisplayName(), "Word: " + word); 31 | assertNotNull(word); 32 | } 33 | 34 | @ParameterizedTest 35 | @EnumSource(TimeUnit.class) 36 | void withAllEnumValues(TimeUnit unit) { 37 | assertNotNull(unit); 38 | } 39 | 40 | @ParameterizedTest 41 | @EnumSource(TimeUnit.class) 42 | void withAllEnumValuesCrossProduct_errors(TimeUnit unit, TimeUnit unit2) { 43 | assertNotNull(unit); 44 | } 45 | 46 | @ParameterizedTest 47 | @EnumSource(value = TimeUnit.class, names = { "NANOSECONDS", "MICROSECONDS" }) 48 | void withSomeEnumValues(TimeUnit unit) { 49 | assertNotNull(unit); 50 | } 51 | 52 | @ParameterizedTest 53 | @MethodSource("createWords") 54 | void withMethodSource(String word) { 55 | assertNotNull(word); 56 | } 57 | 58 | private static Stream createWords() { 59 | return Stream.of("Hello", "Parameterized"); 60 | } 61 | 62 | @ParameterizedTest 63 | @MethodSource("createWordsWithLength") 64 | void testWordLength(String word, int length) { 65 | assertEquals(length, word.length()); 66 | } 67 | 68 | private static Stream createWordsWithLength() { 69 | return Stream.of( 70 | Arguments.of("Hello", 5), 71 | Arguments.of("Parameterized", 13)); 72 | } 73 | 74 | @ParameterizedTest 75 | @CsvSource({ "Hello, 5", "Parameterized, 13", "'Hello, Parameterized!', 21" }) 76 | void withCsvSource(String word, int length) { 77 | assertEquals(length, word.length()); 78 | } 79 | 80 | @ParameterizedTest 81 | @CsvFileSource(resources = "/word-lengths.csv") 82 | void withCsvFileSource(String word, int length) { 83 | assertEquals(length, word.length()); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/parameterized/CustomArgumentConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.parameterized; 2 | 3 | import org.junit.jupiter.api.extension.ParameterContext; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.converter.ArgumentConversionException; 6 | import org.junit.jupiter.params.converter.ArgumentConverter; 7 | import org.junit.jupiter.params.converter.ConvertWith; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | import org.junit.jupiter.params.provider.ValueSource; 10 | 11 | import java.lang.annotation.ElementType; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.RetentionPolicy; 14 | import java.lang.annotation.Target; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.junit.jupiter.api.Assertions.assertNotNull; 18 | 19 | public class CustomArgumentConverterTest { 20 | 21 | @ParameterizedTest 22 | @ValueSource(strings = { "(0/0)", "(0/1)", "(1/0)", "(1/1)", "(2/1)" }) 23 | void testPointNotNull_1(@ConvertWith(PointConverter.class) Point point) { 24 | assertNotNull(point); 25 | } 26 | 27 | @ParameterizedTest 28 | @ValueSource(strings = { "(0/0)", "(0/1)", "(1/0)", "(1/1)", "(2/1)" }) 29 | void testPointNotNull_2(@ConvertPoint Point point) { 30 | assertNotNull(point); 31 | } 32 | 33 | @ParameterizedTest 34 | @CsvSource({ "(0/0), 0", "(0/1), 1", "(1/0), 1", "(1/1), 1.414", "(2/1), 2.236" }) 35 | void testPointNorm(@ConvertPoint Point point, double norm) { 36 | assertEquals(norm, point.norm(), 0.01); 37 | } 38 | 39 | @Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER }) 40 | @Retention(RetentionPolicy.RUNTIME) 41 | @ConvertWith(PointConverter.class) 42 | @interface ConvertPoint { } 43 | 44 | static class PointConverter implements ArgumentConverter { 45 | 46 | @Override 47 | public Object convert(Object input, ParameterContext parameterContext) throws ArgumentConversionException { 48 | if (input instanceof Point) 49 | return input; 50 | if (input instanceof String) 51 | try { 52 | return Point.from((String) input); 53 | } catch (NumberFormatException ex) { 54 | String message = input + " is no correct string representation of a point."; 55 | throw new ArgumentConversionException(message, ex); 56 | } 57 | throw new ArgumentConversionException(input + " is no valid point"); 58 | } 59 | 60 | } 61 | 62 | static class Point { 63 | 64 | final double x, y; 65 | 66 | private Point(double x, double y) { 67 | this.x = x; 68 | this.y = y; 69 | } 70 | 71 | static Point from(double x, double y) { 72 | return new Point(x, y); 73 | } 74 | 75 | static Point from(String x, String y) { 76 | return new Point(Double.parseDouble(x), Double.parseDouble(y)); 77 | } 78 | 79 | static Point from(String xy) { 80 | if (!xy.startsWith("(") || !xy.endsWith(")")) 81 | throw new NumberFormatException(xy + " is no valid point"); 82 | String[] x_y = xy.substring(1, xy.length() - 1).trim().split("/"); 83 | if (x_y.length != 2) 84 | throw new NumberFormatException(xy + " is no valid point"); 85 | return from(x_y[0].trim(), x_y[1].trim()); 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return "(" + x + " / " + y + ")"; 91 | } 92 | 93 | public double norm() { 94 | return Math.sqrt(x * x + y * y); 95 | } 96 | 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/parameterized/CustomArgumentsSourceTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.parameterized; 2 | 3 | import org.junit.jupiter.api.extension.ExtensionContext; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.ArgumentsProvider; 7 | import org.junit.jupiter.params.provider.ArgumentsSource; 8 | 9 | import java.util.Random; 10 | import java.util.stream.Stream; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | 14 | public class CustomArgumentsSourceTest { 15 | 16 | @ParameterizedTest 17 | @ArgumentsSource(RandomIntegerProvider.class) 18 | void testWithArgumentsSource(Integer argument) { 19 | assertNotNull(argument); 20 | } 21 | 22 | static class RandomIntegerProvider implements ArgumentsProvider { 23 | 24 | @Override 25 | public Stream provideArguments(ExtensionContext context) { 26 | return new Random() 27 | .ints(0, 10) 28 | .mapToObj(Arguments::of) 29 | .limit(3); 30 | } 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/parameterized/HelloParams.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.parameterized; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.ValueSource; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertNotNull; 7 | 8 | class HelloParams { 9 | 10 | @ParameterizedTest 11 | void withoutSource_errors(String word) { 12 | assertNotNull(word); 13 | } 14 | 15 | @ParameterizedTest 16 | @ValueSource(strings = { "Hello", "JUnit" }) 17 | void withValueSource(String word) { 18 | assertNotNull(word); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/parameterized/MetaAnnotationTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.parameterized; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.CsvSource; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | 9 | public class MetaAnnotationTest { 10 | 11 | @Params 12 | void testMetaAnnotation(String s, int i) { 13 | System.out.println(s + ": " + i); 14 | } 15 | 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @ParameterizedTest(name = "Elaborate name listing all {arguments}") 18 | @CsvSource({ "Foo, 1", "Bar, 2" }) 19 | @interface Params { 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/parameterized/NameTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.parameterized; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.CsvSource; 6 | 7 | class NameTest { 8 | 9 | @ParameterizedTest 10 | @CsvSource({ "One, 1", "Two, 2" }) 11 | void testDefault(String word, int number) { } 12 | 13 | @ParameterizedTest(name = "run #{index} with [{arguments}]") 14 | @CsvSource({ "One, 1", "Two, 2" }) 15 | void testVerbose(String word, int number) { } 16 | 17 | @ParameterizedTest(name = "{index}") 18 | @CsvSource({ "One, 1", "Two, 2" }) 19 | void testIndex(String word, int number) { } 20 | 21 | @ParameterizedTest(name = "{0}: {1}") 22 | @CsvSource({ "One, 1", "Two, 2" }) 23 | void testSpecificArguments(String word, int number) { } 24 | 25 | @ParameterizedTest(name = "{arguments}") 26 | @CsvSource({ "One, 1", "Two, 2" }) 27 | void testAllArguments(String word, int number) { } 28 | 29 | @DisplayName("Roman numeral") 30 | @ParameterizedTest(name = "\"{0}\" should be {1}") 31 | @CsvSource({ "I, 1", "II, 2", "V, 5" }) 32 | void testWithDisplayName(String roman, int arabic) { } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/scenario/ClassTestInstanceTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.scenario; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.TestInstance; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; 8 | 9 | @TestInstance(PER_CLASS) 10 | class ClassTestInstanceTest { 11 | 12 | private static int CLASS_TEST_COUNT = 0; 13 | 14 | private int instanceTestCount = 0; 15 | 16 | @Test 17 | void testOnInstance_1() { 18 | incrementAndCompareTestCounts(); 19 | } 20 | 21 | @Test 22 | void testOnInstance_2() { 23 | incrementAndCompareTestCounts(); 24 | } 25 | 26 | @Test 27 | void testOnInstance_3() { 28 | incrementAndCompareTestCounts(); 29 | } 30 | 31 | private void incrementAndCompareTestCounts() { 32 | CLASS_TEST_COUNT++; 33 | instanceTestCount++; 34 | // this assertion would fail if a new instance 35 | // would be created for each test method 36 | assertThat(instanceTestCount).isEqualTo(CLASS_TEST_COUNT); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/scenario/ScenarioExtensionTests.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.scenario; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | @ScenarioTest 9 | class ScenarioExtensionTests { 10 | 11 | private boolean loggedIn; 12 | private List actions = new ArrayList<>(); 13 | 14 | @Step 15 | void loginTest() { 16 | loggedIn = true; 17 | } 18 | 19 | @Step(after = "loginTest" ) 20 | void action_A() { 21 | assertThat(loggedIn).isTrue(); 22 | actions.add("a"); 23 | } 24 | 25 | @Step(after = "action_A") 26 | void action_B() { 27 | assertThat(loggedIn).isTrue(); 28 | assertThat(actions.size()).isGreaterThan(0); 29 | actions.add("b"); 30 | throw new RuntimeException(); 31 | } 32 | 33 | @Step(after = "action_A") 34 | void action_C() { 35 | assertThat(loggedIn).isTrue(); 36 | assertThat(actions.size()).isGreaterThan(0); 37 | actions.add("c"); 38 | } 39 | 40 | @Step(after = { "action_A", "action_B", "action_C" }, next = "verify" ) 41 | void logoutTest() { 42 | assertThat(loggedIn).isTrue(); 43 | loggedIn = false; 44 | } 45 | 46 | @Step 47 | void verify() { 48 | assertThat(loggedIn).isFalse(); 49 | assertThat(actions).hasSize(3); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/demo/junit5/templates/RepeatedInvocationTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.demo.junit5.templates; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.RepeatedTest; 5 | import org.junit.jupiter.api.RepetitionInfo; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | class RepeatedInvocationTest { 10 | 11 | private static int REPETITION_COUNT = 0; 12 | 13 | @RepeatedTest(5) 14 | void repeated(RepetitionInfo repetitions) { 15 | REPETITION_COUNT++; 16 | // RepetitionInfo::getCurrentRepetition starts with 1 17 | assertThat(repetitions.getCurrentRepetition()).isEqualTo(REPETITION_COUNT); 18 | assertThat(repetitions.getTotalRepetitions()).isEqualTo(5); 19 | } 20 | 21 | @DisplayName("Calling repeated...") 22 | @RepeatedTest( 23 | value = 5, 24 | name = "... {currentRepetition}th " 25 | + "of {totalRepetitions} times") 26 | void repeatedWithDisplayName(RepetitionInfo repetitions) { } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | #junit.jupiter.conditions.deactivate=* 2 | -------------------------------------------------------------------------------- /src/test/resources/word-lengths.csv: -------------------------------------------------------------------------------- 1 | Hello, 5 2 | Parameterized, 13 3 | "Hello, Parameterized!", 21 4 | --------------------------------------------------------------------------------