[JAVA] How to migrate from JUnit4 to JUnit5

Recently, I was looking for a way to move from JUnit4 to JUnit5, but I found it difficult to search for materials here and there, so I decided to summarize it.

Policy on how to proceed with the transition

For a small project, I think it's possible to change the notation from JUnit4 to JUnit5 at once, but for a large project, the quantity is strict, so basically I think it's stable to proceed as follows. ..

  1. Update the JUnit library Remove JUnit4 and add JUnit5 (* However, in order to keep JUnit4 running, junit-vintage-engine is also included)
  2. Rewrite existing tests in JUnit4 notation with JUnit5 notation (New tests will be written in JUnit5 notation)
  3. Remove junit-vintage-engine from dependencies when all tests are in JUnit5 notation

The second one above works as long as junit-vintage-engine is in the dependent library, but I think it will come when I have to do it, so at any time.

By the way, the official migration method of JUnit is → https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4

Specific procedure

Library update

First, switch to the JUnit5 library (the following is Maven, but in the case of Gradle, please change it appropriately [^ gradle]) [^ gradle]: For Gradle settings, refer to the official https://github.com/junit-team/junit5-samples/tree/main/junit5-migration-gradle.

I think it was originally written in JUnit4 as follows.

pom.xml(JUnit4)


<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

Instead of the above content, put the following

pom.xml(JUnit5)


<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>

By the way, I'm using InteliJ, so I added the following (Just looking at the official message Only needed to run tests in a version of IntelliJ IDEA that bundles older versions, I wondered if the new InteliJ wouldn't have to be included.

pom.xml(JUnit5)


<!-- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -->
<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-launcher</artifactId>
    <version>1.7.0</version>
    <scope>test</scope>
</dependency>

By the way, the meaning of each is as follows with reference to Official explanation. It feels like I'll add others when I need it.

org.junit.jupiter junit-jupiter-engine
JUnit Jupiter test engine (runtime)
org.junit.jupiter junit-jupiter-api
Various APIs for test description (`@Test`,` @BeforeEach`, etc. [Details to official](https://junit.org/junit5/docs/current/user-guide/#writing-tests))
org.junit.vintage junit-vintage-engine
Test engine that makes JUnit3 and JUnit4 executable

Rewriting the test from JUnit4 to JUnit5

Personally, it was quite easy for JUnit4's @Rule to disappear.

Simple rewrite 1 (Package change)

I used to use the one under org.junit in JUnit4, but in JUnit5 it becomes the one under org.junit.jupiter.api, so simply rewrite the Package of import.

Target JUnit4 Package JUnit5 Package
@TestAnd org.junit.* org.junit.jupiter.api.*
fail()And org.junit.Assert.* org.junit.jupiter.api.Assertions.*

Simple rewrite 2 (replacement of annotation)

The annotation has changed, so replace it

JUnit4 JUnit5
@Before @BeforeEach
@After @AfterEach
@BeforeClass @BeforeAll
@AfterClass @AfterAll
@Ignore @Disabled
@Category @Tag

In addition, the ones that can not be rewritten simply are as follows.

--Switching from @ RunWith to @ ExtendWith --Switching from @Rule or @ClassRule to @ExtendWith or @RegisterExtension

Change the way tests are layered

I think JUnit4 used inner class and @ RunWith (Enclosed.class) to organize the tests (like below).

How to organize JUnit4 tests


@RunWith(Enclosed.class)
public class SampleJUnit4Test {

  public static class TestCategoryA {
    @Test
    public void testA1() { ... }
  }

  public static class TestCategoryB {
    @Test
    public void testB1() { ... }
  }
}

JUnit5 allows hierarchies with @ Nested and non-static classes. I wondered if the notation became straightforward (impression).

How to organize JUnit 5 tests


public class SampleJUnit5Test {

  @Nested
  class TestCategoryA {
    @Test
    public void testA1() { ... }
  }

  @Nested
  class TestCategoryB {
    @Test
    public void testB1() { ... }
  }
}

As a caveat, if you add @Nested during the migration process from JUnit4 to JUnit5 but keep it as a static class, a compile error will not occur, but the test execution will be ignored. Please be careful. (I haven't checked the details)

Correspondence of the part where BeforeAll and AfterAll are done in the inner class

I think I wrote the following in the foreground,

  • However, if you use @BeforeClass or @AfterClass in the inner class due to test hierarchy etc., you need a little more annotation, so I will write it separately below)

When migrating JUnit5, the inner class will no longer be static by using a test hierarchy using @ Nested. Therefore, if you do @BeforeClass or @AfterClass in the inner class, an error will occur.

In this case, if you add @TestInstance (Lifecycle.PER_CLASS) to the target class according to the contents of Official description, a test instance will be created for each test class, and in the inner class You will be able to use @BeforeAll and @AfterAll.

Use All system in inner class in JUnit5


public class SampleJUnit5Test {

  @Nested
  @TestInstance(Lifecycle.PER_CLASS)
  class TestCategory {

    @BeforeAll
    static void beforeAll() { ... }
  }
}

Change Assert method for Exception

Since the Assert method of Exception type has changed, I will change it as well. In JUnit4, I think I used @Test (expected = SampleException.class) and ExpectedException.

For JUnit 4


@Test(expected = NullPointerException.class)
public void NullPointerException occurs() {
    /*Tests that expect a NullPointerException*/
}

For JUnit 4


@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void SampleException occurs() {
    expectedException.expect(SampleException.class);
    expectedException.expectMessage("SampleException message");

    /*Tests that expect SampleException to occur*/
}

For JUnit5, use assertThrows. In the argument of assetThrows, pass the class of Exception that is expected to occur and the test code (via lambda expression).

For JUnit 5


@Test
public void NullPointerException occurs() {
    assertThrows(NullPointerException.class, () -> {
        /*Tests that expect a NullPointerException*/
    });
}

If you only want to check the occurrence of exceptions, you can use only assertThrows, but if you want to test the details of the Exception that occurred including the error message, receive the Exception returned by assertThrows and write various validations.

For JUnit 5


@Test
public void SampleException occurs() {
    SampleException exception = assertThrows(SampleException.class, () -> {
        /*Tests that expect SampleException to occur*/
    });
    assertEquals("SampleException message", exception.getMessage());
}

Changes in how to write exams using TemporaryFolder

In JUnit4, I think there is a place where the test is written using @Rule + TemporaryFolder for the test around the file. In JUnit5, @Rule is gone, so this is also a fix.

It's very simple, though, and what was written in JUnit4 as follows

python


@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();

private Path root;

@Before
public void setup() {
    root = temporaryFolder.getRoot().toPath().toAbsolutePath();
}

Change it to the following (Note that variables with @ TempDir will cause an error if they are private)

python


@TempDir
Path root;

I feel that this is a good feeling because the annoying things have disappeared.

others

Recommended Posts

How to migrate from JUnit4 to JUnit5
How to use JUnit 5
How to use JUnit (beginner)
How to write Junit 5 organized
[Creating] How to use JUnit
How to push from Tarminal to GitHub
How to run JUnit in Eclipse
Migrate from on-premise Pukiwiki to esa.io \ (⁰⊖⁰) /
How to change from HTML to Haml
[IOS] How to get data from DynamoDB
How to call Swift 5.3 code from Objective-C
How to deploy
How to fix system date in JUnit
[Flutter] How to use C / C ++ from Dart?
Java: How to send values from Servlet to Servlet
How to test private scope with JUnit
[Ruby] How to convert from lowercase to uppercase and from uppercase to lowercase
[Java] How to test for null with JUnit
How to execute and mock methods using JUnit
How to link Rails6 Vue (from environment construction)
How to test interrupts during Thread.sleep with JUnit
Migrate from Java to Server Side Kotlin + Spring-boot
JUnit 5: How to write test cases in enum
How to get Class from Element in Java
Notes on how to use each JUnit Rule
[Java] How to switch from open jdk to oracle jdk
How to get SIMD optimization from HotSpot JavaVM
[ruby] How to receive values from standard input?
[Swift, ARKit] Migrate from deprecated hitTest to raycastQuery
How to develop OpenSPIFe
How to call AmazonSQSAsync
How to use Map
How to write Rails
How to use rbenv
How to use letter_opener_web
How to use with_option
How to use fields_for
How to use map
How to use collection_select
How to adapt Bootstrap
How to use Twitter4J
How to use active_hash! !!
How to install Docker
How to use MapStruct
How to use hidden_field_tag
How to use TreeSet
How to write dockerfile
How to uninstall Rails
How to install docker-machine
[How to use label]
How to write docker-compose
How to use identity
How to use hashes
How to write Mockito
From Java to Ruby !!
How to install MySQL
How to write migrationfile
How to build android-midi-lib
How to use Dozer.mapper
How to use Gradle
How to use org.immutables