Migrating from JUnit 4
Although the JUnit Jupiter programming model and extension model do not support JUnit 4
features such as Rules and Runners natively, it is not expected that source code
maintainers will need to update all of their existing tests, test extensions, and custom
build test infrastructure to migrate to JUnit Jupiter.
Instead, JUnit provides a gentle migration path via a JUnit Vintage test engine which
allows existing tests based on JUnit 3 and JUnit 4 to be executed using the JUnit Platform
infrastructure. Since all classes and annotations specific to JUnit Jupiter reside under
the org.junit.jupiter base package, having both JUnit 4 and JUnit Jupiter in the
classpath does not lead to any conflicts. It is therefore safe to maintain existing JUnit
4 tests alongside JUnit Jupiter tests and migrate them gradually.
Running JUnit 4 Tests on the JUnit Platform
|
The JUnit Vintage engine is deprecated and should only be used temporarily while migrating tests to JUnit Jupiter or another testing framework with native JUnit Platform support. By default, if the JUnit Vintage engine is registered and discovers at least one test
class, it reports a discovery issue of INFO severity.
You can prevent this discovery issue from being reported by setting the
|
Make sure that the junit-vintage-engine artifact is in your test runtime path. In that
case JUnit 3 and JUnit 4 tests will automatically be picked up by the JUnit Platform
launcher.
See the example projects in the junit-examples repository to
find out how this is done with Gradle and Maven.
Categories Support
For test classes or methods that are annotated with @Category, the JUnit Vintage test
engine exposes the category’s fully qualified class name as a tag
for the corresponding test class or test method. For example, if a test method is
annotated with @Category(Example.class), it will be tagged with "com.acme.Example".
Similar to the Categories runner in JUnit 4, this information can be used to filter the
discovered tests before executing them (see Running Tests for details).
Parallel Execution
The JUnit Vintage test engine supports parallel execution of top-level test classes and test methods, allowing existing JUnit 3 and JUnit 4 tests to benefit from improved performance through concurrent test execution. It can be enabled and configured using the following configuration parameters:
junit.vintage.execution.parallel.enabled=true|false-
Enable/disable parallel execution (defaults to
false). Requires opt-in forclassesormethodsto be executed in parallel using the configuration parameters below. junit.vintage.execution.parallel.classes=true|false-
Enable/disable parallel execution of test classes (defaults to
false). junit.vintage.execution.parallel.methods=true|false-
Enable/disable parallel execution of test methods (defaults to
false). junit.vintage.execution.parallel.pool-size=<number>-
Specifies the size of the thread pool to be used for parallel execution. By default, the number of available processors is used.
Parallelization at Class Level
Let’s assume we have two test classes FooTest and BarTest with each class containing
three unit tests. Now, let’s enable parallel execution of test classes:
junit.vintage.execution.parallel.enabled=true
junit.vintage.execution.parallel.classes=true
With this setup, the VintageTestEngine will use two different threads,
one for each test class:
ForkJoinPool-1-worker-1 - BarTest::test1
ForkJoinPool-1-worker-2 - FooTest::test1
ForkJoinPool-1-worker-1 - BarTest::test2
ForkJoinPool-1-worker-2 - FooTest::test2
ForkJoinPool-1-worker-1 - BarTest::test3
ForkJoinPool-1-worker-2 - FooTest::test3
Parallelization at Method Level
Alternatively, we can enable parallel test execution at a method level, rather than the class level:
junit.vintage.execution.parallel.enabled=true
junit.vintage.execution.parallel.methods=true
Therefore, the test methods within each class will be executed in parallel, while different test classes will be executed sequentially:
ForkJoinPool-1-worker-1 - BarTest::test1
ForkJoinPool-1-worker-2 - BarTest::test2
ForkJoinPool-1-worker-3 - BarTest::test3
ForkJoinPool-1-worker-3 - FooTest::test1
ForkJoinPool-1-worker-2 - FooTest::test2
ForkJoinPool-1-worker-1 - FooTest::test3
Full Parallelization
Finally, we can also enable parallelization at both class and method level:
junit.vintage.execution.parallel.enabled=true
junit.vintage.execution.parallel.classes=true
junit.vintage.execution.parallel.methods=true
With these properties set, the VintageTestEngine will execute all tests classes and
methods in parallel, potentially significantly reducing the overall test suite execution time:
ForkJoinPool-1-worker-6 - FooTest::test2
ForkJoinPool-1-worker-7 - BarTest::test3
ForkJoinPool-1-worker-3 - FooTest::test1
ForkJoinPool-1-worker-8 - FooTest::test3
ForkJoinPool-1-worker-5 - BarTest::test2
ForkJoinPool-1-worker-4 - BarTest::test1
Configuring the Pool Size
The default thread pool size is equal to the number of available processors. However, we can also configure the pool size explicitly:
junit.vintage.execution.parallel.enabled=true
junit.vintage.execution.parallel.classes=true
junit.vintage.execution.parallel.methods=true
junit.vintage.execution.parallel.pool-size=4
For instance, if we update our previous example that uses full parallelization and configure the pool size to four, we can expect to see our six test methods executed with a parallelism of four:
ForkJoinPool-1-worker-2 - FooTest::test1
ForkJoinPool-1-worker-4 - BarTest::test2
ForkJoinPool-1-worker-3 - BarTest::test1
ForkJoinPool-1-worker-4 - BarTest::test3
ForkJoinPool-1-worker-2 - FooTest::test2
ForkJoinPool-1-worker-3 - FooTest::test3
As we can see, even though we set the thread pool size was four, only three threads were used in this case. This happens because the pool adjusts the number of active threads based on workload and system needs.
Sequential Execution
On the other hand, if we disable parallel execution, the VintageTestEngine
will execute all tests sequentially, regardless of the other properties:
junit.vintage.execution.parallel.enabled=false
junit.vintage.execution.parallel.classes=true
junit.vintage.execution.parallel.methods=true
Similarly, tests will be executed sequentially if you enable parallel execution in general but enable neither class-level nor method-level parallelization.
Migration Tips
The following are topics that you should be aware of when migrating existing JUnit 4 tests to JUnit Jupiter.
-
Annotations reside in the
org.junit.jupiter.apipackage. -
Assertions reside in
org.junit.jupiter.api.Assertions. -
Assumptions reside in
org.junit.jupiter.api.Assumptions.-
Note that JUnit Jupiter supports methods from JUnit 4’s
org.junit.Assumeclass for assumptions. Specifically, JUnit Jupiter supports JUnit 4’sAssumptionViolatedExceptionto signal that a test should be aborted instead of marked as a failure.
-
-
@Beforeand@Afterno longer exist; use@BeforeEachand@AfterEachinstead. -
@BeforeClassand@AfterClassno longer exist; use@BeforeAlland@AfterAllinstead. -
@Ignoreno longer exists: use@Disabledor one of the other built-in execution conditions instead-
See also JUnit 4 @Ignore Support.
-
-
@Categoryno longer exists; use@Taginstead. -
@RunWithno longer exists; superseded by@ExtendWith.-
For
@RunWith(Enclosed.class)use@Nested. -
For
@RunWith(Parameterized.class)see Parameterized test classes.
-
-
@Ruleand@ClassRuleno longer exist; superseded by@ExtendWithand@RegisterExtension.-
See also Limited JUnit 4 Rule Support.
-
-
@Test(expected = …)and theExpectedExceptionrule no longer exist; useAssertions.assertThrows(…)instead.-
See Limited JUnit 4 Rule Support if you still need to use
ExpectedException.
-
-
Assertions and assumptions in JUnit Jupiter accept the failure message as their last argument instead of the first one.
-
See Failure Message Arguments for details.
-
Parameterized test classes
Unless @UseParametersRunnerFactory is used, a JUnit 4 parameterized test class can be
converted into a JUnit Jupiter
@ParameterizedClass by following these steps:
-
Replace
@RunWith(Parameterized.class)with@ParameterizedClass. -
Add a class-level
@MethodSource("methodName")annotation wheremethodNameis the name of the method annotated with@Parametersand remove the@Parametersannotation from the method. -
Replace
@BeforeParamand@AfterParamwith@BeforeParameterizedClassInvocationand@AfterParameterizedClassInvocation, respectively, if there are any methods with such annotations. -
Change the imports of the
@Testand@Parameterannotations to use theorg.junit.jupiter.paramspackage. -
Change assertions etc. to use the
org.junit.jupiter.apipackage as usual. -
Optionally, remove all
publicmodifiers from the class and its methods and fields.
@RunWith(Parameterized.class)
public class JUnit4ParameterizedClassTests {
@Parameterized.Parameters
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 1, "foo" }, { 2, "bar" } });
}
@Parameterized.Parameter(0)
public int number;
@Parameterized.Parameter(1)
public String text;
@Parameterized.BeforeParam
public static void before(int number, String text) {
}
@Parameterized.AfterParam
public static void after() {
}
@org.junit.Test
public void someTest() {
}
@org.junit.Test
public void anotherTest() {
}
}
@ParameterizedClass
@MethodSource("data")
class JupiterParameterizedClassTests {
static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 1, "foo" }, { 2, "bar" } });
}
@org.junit.jupiter.params.Parameter(0)
int number;
@org.junit.jupiter.params.Parameter(1)
String text;
@BeforeParameterizedClassInvocation
static void before(int number, String text) {
}
@AfterParameterizedClassInvocation
static void after() {
}
@org.junit.jupiter.api.Test
void someTest() {
}
@org.junit.jupiter.api.Test
void anotherTest() {
}
}
Limited JUnit 4 Rule Support
| JUnit 4 rule support is deprecated for removal since version 6.0.0. Please migrate to the corresponding APIs and extensions provided by JUnit Jupiter. |
As stated above, JUnit Jupiter does not and will not support JUnit 4 rules natively. The JUnit team realizes, however, that many organizations, especially large ones, are likely to have large JUnit 4 code bases that make use of custom rules. To serve these organizations and enable a gradual migration path the JUnit team has decided to support a selection of JUnit 4 rules verbatim within JUnit Jupiter. This support is based on adapters and is limited to those rules that are semantically compatible to the JUnit Jupiter extension model, i.e. those that do not completely change the overall execution flow of the test.
The junit-jupiter-migrationsupport module from JUnit Jupiter currently supports the
following three Rule types including subclasses of these types:
-
org.junit.rules.ExternalResource(includingorg.junit.rules.TemporaryFolder) -
org.junit.rules.Verifier(includingorg.junit.rules.ErrorCollector) -
org.junit.rules.ExpectedException
As in JUnit 4, Rule-annotated fields as well as methods are supported. By using these
class-level extensions on a test class such Rule implementations in legacy code bases
can be left unchanged including the JUnit 4 rule import statements.
This limited form of Rule support can be switched on by the class-level annotation
@EnableRuleMigrationSupport. This annotation is a composed annotation which enables
all rule migration support extensions: VerifierSupport, ExternalResourceSupport, and
ExpectedExceptionSupport. You may alternatively choose to annotate your test class with
@EnableJUnit4MigrationSupport which registers migration support for rules and JUnit
4’s @Ignore annotation (see JUnit 4 @Ignore Support).
However, if you intend to develop a new extension for JUnit Jupiter please use the new extension model of JUnit Jupiter instead of the rule-based model of JUnit 4.
JUnit 4 @Ignore Support
JUnit 4 @Ignore support is deprecated for removal since version 6.0.0. Please
use JUnit Jupiter’s @Disabled annotation instead.
|
In order to provide a smooth migration path from JUnit 4 to JUnit Jupiter, the
junit-jupiter-migrationsupport module provides support for JUnit 4’s @Ignore
annotation analogous to Jupiter’s @Disabled annotation.
To use @Ignore with JUnit Jupiter based tests, configure a test dependency on the
junit-jupiter-migrationsupport module in your build and then annotate your test class
with @ExtendWith(IgnoreCondition.class) or @EnableJUnit4MigrationSupport (which
automatically registers the IgnoreCondition along with
Limited JUnit 4 Rule Support). The IgnoreCondition is an
ExecutionCondition that disables test classes or test methods that are annotated with
@Ignore.
import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport;
// @ExtendWith(IgnoreCondition.class)
@SuppressWarnings("removal")
@EnableJUnit4MigrationSupport
class IgnoredTestsDemo {
@Ignore
@Test
void testWillBeIgnored() {
}
@Test
void testWillBeExecuted() {
}
}
Failure Message Arguments
The Assumptions and Assertions classes in JUnit Jupiter declare arguments in a
different order than in JUnit 4. In JUnit 4 assertion and assumption methods accept the
failure message as the first argument; whereas, in JUnit Jupiter assertion and assumption
methods accept the failure message as the last argument.
For instance, the method assertEquals in JUnit 4 is declared as assertEquals(String
message, Object expected, Object actual), but in JUnit Jupiter it is declared as
assertEquals(Object expected, Object actual, String message). The rationale for this is
that a failure message is optional, and optional arguments should be declared after
required arguments in a method signature.
The methods affected by this change are the following:
-
Assertions
-
assertTrue -
assertFalse -
assertNull -
assertNotNull -
assertEquals -
assertNotEquals -
assertArrayEquals -
assertSame -
assertNotSame -
assertThrows
-
-
Assumptions
-
assumeTrue -
assumeFalse
-