├── .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 extends Exception>[] 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 extends Exception>[] 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 extends Exception>[] 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 extends Exception> 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 extends Class extends Throwable>> 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 extends Throwable> 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 extends Arguments> 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 |
--------------------------------------------------------------------------------