アルファテックブログ

OpenRewriteを使ってJUnit4からJUnit5に移行する

カバー

目次

1. はじめに

1.1. 背景

2025年11月13日にSpring Framework 7がリリースされました。 Spring Framework 7からJUnit4が非推奨となり、次回以降のSpringのバージョンアップでJUnit4の関連機能が削除される旨が示唆されています。

これを受け、Spring TestのJUnit4の機能を使ってテストコードを書いているアプリケーションは、JUnit5/JUnit6への移行が推奨されています。

1.2. 移行手順

JUnit4からJUnit5への移行には、以下のような多くの修正が必要となります。

  • パッケージ名の変更
  • アノテーションの変更
  • ロジック自体の変更

これらをすべて手動で行うのは大変な作業ですが、OpenRewriteを使用することで、自動である程度の変換を行うことができます。

JUnit5とJUnit6の違いについては以下のような変更が主なので、大きな違いはありません。

  • Javaの最低バージョンがJava8からJava17に変更
  • いくつかの非推奨メソッドの削除

また、OpenRewriteではJUnit4からJUnit6に直接移行できません。以上のことから、OpenRewriteを用いたJUnit4からJUnit5への移行方法を説明します。

この記事では以下の手順で移行を行いました。

  1. OpenRewriteで自動変換を実行
  2. JUnit公式ドキュメントのMigrating from JUnit 4を参照
  3. GitHub Copilotや置換を活用して残りを手動で修正

1.3. 対象環境

この記事では以下の環境を前提とします。

  • Java 17
  • Mavenプロジェクト
  • Spring Frameworkを使用

2. OpenRewriteについて

2.1. OpenRewriteとは

OpenRewriteは、ソースコード用のオープンソースの自動リファクタリングツールです。

この記事ではOpenRewriteを使用したJUnit4からJUnit5への移行方法を説明します。なお、OpenRewriteはJava 17からJava 21、Spring Boot 2からSpring Boot 3への移行など、様々なリファクタリングにも活用できます。
詳細はPopular recipe guidesを参照してください。

2.2. OpenRewriteの使い方

POMファイルにプラグインの設定を追加し、Mavenコマンドを実行することで起動が可能となります。

  1. pom.xmlのpluginManagementにrewrite-maven-pluginを追加する。

今回使用したrewrite-maven-pluginのバージョンは6.29.0(OpenRewrite自体のバージョンは8.73.0)、rewrite-springのバージョンは6.24.1です。

<pluginManagement>
<plugins>
<!-- 他のプラグイン -->
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>{rewrite-maven-plugin.version}</version>
<configuration>
<activeRecipes>
<recipe>org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration</recipe>
</activeRecipes>
</configuration>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-spring</artifactId>
<version>{rewrite-spring.version}</version>
</dependency>
</dependencies>
</plugin>
<!-- 他のプラグイン -->
</plugins>
</pluginManagement>
  1. mvn rewrite:runを実行する。
Terminal window
$ mvn rewrite:run
(omitted)
[INFO]
[INFO] <<< rewrite:6.29.0:run (default-cli) < process-test-classes @ sample <<<
[INFO]
[INFO]
[INFO] --- rewrite:6.29.0:run (default-cli) @ sample ---
[INFO] Using active recipe(s) [org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration]
[INFO] Using active styles(s) []
[INFO] Validating active recipes...
[INFO] Project [Sample Project] Resolving Poms...
[INFO] Project [Sample Project] Parsing source files
(omitted)
[WARNING] Changes have been made to sample\src\test\java\com\example\sample\SampleTest.java by:
[WARNING] org.openrewrite.java.testing.junit5.JUnit4to5Migration
[WARNING] org.openrewrite.java.testing.junit5.RemoveObsoleteRunners
[WARNING] org.openrewrite.java.testing.junit5.UpdateTestAnnotation
[WARNING] Changes have been made to sample\src\test\java\com\example\sample\error\ErrorTest.java by:
[WARNING] org.openrewrite.java.testing.junit5.JUnit4to5Migration
[WARNING] org.openrewrite.java.testing.junit5.RemoveObsoleteRunners
[WARNING] org.openrewrite.java.testing.junit5.UpdateTestAnnotation
(omitted)
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 1h 35m
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 28.394 s
[INFO] Finished at: 2026-02-17T10:56:22+09:00
[INFO] ------------------------------------------------------------------------

移行したファイルはWARNINGで出力されます。Mavenコマンドが正常に完了しても、JUnit4からJUnit5への移行が完全に完了したとは限らないため、中身を確認する必要があります。OpenRewriteで自動修正されなかった箇所については、手動で修正する必要があります。
なお、OpenRewriteはカスタムレシピを追加することで、デフォルトでは対応できない箇所も自動変換できるようになりますが、この記事ではデフォルトのレシピを使用しています。

以降では、

について説明します。

3. 自動で移行される箇所

OpenRewriteを使用して自動で移行される主な箇所は以下の通りです。

なお、動作上は問題なくても、可読性の観点から追加のリファクタリングが必要となる場合があります。

3.1. pom.xmlの修正

pom.xmlについては、以下の修正が自動で行われます。

  • JUnit4の依存関係(junit:junit)の削除
  • JUnit5の依存関係(org.junit.jupiter:junit-jupiter)の追加
  • OSSが内部で持つJUnit4の依存関係(junit:junit)の除外

JUnit4の依存関係(junit:junit)の削除、JUnit5の依存関係(org.junit.jupiter:junit-jupiter)の追加

JUnit4
以下が削除されます。

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

JUnit5
以下が追加されます。

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

OSSが内部で持つJUnit4の依存関係(junit:junit)の除外

例として、以下のような依存関係の除外が追加されます。

<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail-spring</artifactId>
<scope>runtime</scope>
<!-- JUnit4の除外 -->
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>

ただし、削除したJUnit4の定義が記述されていた箇所とは別の箇所にJUnit5の定義が追加されていたり、JUnit4の定義の前後に<!-- -->でコメントが追加されていた場合などはコメントが削除されている可能性があるため、手動での修正が必要となることがあります。

3.2. パッケージ

JUnit4からJUnit5の変更に伴いパッケージ名が変更されています。
そのため、インポート文は以下のように修正されます。

JUnit4

import org.junit.Test;

JUnit5

import org.junit.jupiter.api.Test;

3.3. アノテーション

パッケージだけではなく、各種アノテーションの名前も変更されています。

  • @Before@BeforeEach
  • @After@AfterEach
  • @BeforeClass@BeforeAll
  • @AfterClass@AfterAll
  • @Ignore@Disabled

3.4. 例外を期待するテスト

JUnit4では、@Test(expected = Exception.class)のように@Testアノテーションに期待する例外を設定できましたが、JUnit5ではexpectedが用意されていません。 JUnit5ではassertThrowsを使用して例外を検証します。
https://docs.junit.org/current/writing-tests/exception-handling.html#expected

OpenRewriteを使用すると以下のように変更されます。

JUnit4

@Test(expected = Exception.class)
public void testException() {
// 例外をスローするコード
}
@Test(expected = None.class)
public void testNoException() {
// 例外をスローしないコード
}

JUnit5

@Test
public void testException() {
assertThrows(Exception.class, () -> {
// 例外をスローするコード
});
}
@Test
public void testNoException() {
assertDoesNotThrow(() -> {
// 例外をスローしないコード
});
}

expectedを使用している箇所はOpenRewriteを使用すれば自動で移行されますが、assertThrowsがメソッド全体を囲むように修正されてしまいます。
expectedはテストメソッドがスローする例外を判定しているだけなので、全体をassertThrowsで囲むことが同等の修正となりますが、assertThrowsではエラー発生箇所を限定することが可能であるため、事前条件等がassertThrowsの範囲外になるように手動で修正することをお勧めします。

JUnit4

@Test(expected = Exception.class)
public void testException() {
initialize();
throwException(); // 例外をスローするコード
deleteData();
}

JUnit5

@Test
public void testException() {
initialize();
assertThrows(Exception.class, () -> {
throwException(); // 例外をスローするコード
});
deleteData();
}

なお、assertThrowsはスローされた型のサブクラスもチェックするため、厳密に型をチェックしたい場合はassertThrowsExactlyを使用することもできます。試験内容上、例外の型を限定しなければならない場合は手動でassertThrowsExactlyに修正する必要があります。

3.5. Spring Testの変更

JUnitでSpringのDIやテストコンテキストを使用する場合、JUnit4とJUnit5でアノテーションの使用方法が異なります。

JUnit4
JUnit4では、@RunWith(SpringJUnit4ClassRunner.class)@ContextConfigurationを使用していました。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MyConfig.class})
public class MyTests {
}

JUnit5
JUnit5では、@ExtendWith(SpringExtension.class)@ContextConfigurationを使用します。
https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/support-classes.html#testcontext-junit-jupiter-extension

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {MyConfig.class})
public class MyTests {
}

@ExtendWith(SpringExtension.class)@ContextConfiguration()をまとめて@SpringJUnitConfigとすることもできます。OpenRewriteを使用した場合、まとめることができるものは@SpringJUnitConfigに修正されます。
https://docs.spring.io/spring-framework/reference/testing/annotations/integration-junit-jupiter.html#integration-testing-annotations-junit-jupiter-springjunitconfig

@SpringJUnitConfig(classes = {MyConfig.class})
public class MyTests {
}

@ContextHierarchyの中に@ContextConfigurationがある場合は、@SpringJUnitConfigを使用することができません。この場合は@RunWith(SpringJUnit4ClassRunner.class)@ExtendWith(SpringExtension.class)に修正され、@ContextConfigurationはそのまま残ります。@ContextHierarchyを使用している場合は、以下のように修正されます。

JUnit4

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", classes = {ParentConfig.class}),
@ContextConfiguration(name = "child", classes = {ChildConfig.class})
})
public class MyTests {
}

JUnit5

@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", classes = {ParentConfig.class}),
@ContextConfiguration(name = "child", classes = {ChildConfig.class})
})
public class MyTests {
}

3.6. TestName

テストの中でテスト名を取得する場合、JUnit4とJUnit5で方法が異なります。

JUnit4
JUnit4では、@Ruleを使用してTestNameクラスのインスタンスをテストクラスに登録していました。

@Rule
public TestName testName = new TestName();

JUnit5
JUnit5では、TestInfoを使用してテストメソッド名を取得することができます。TestInfo@BeforeEach@Testメソッドの引数として渡すことができます。OpenRewriteを使用した場合、TestInfoを引数に取る@BeforeEachメソッドが追加され、テストメソッド名がフィールドに格納されるようになります。
https://docs.junit.org/current/writing-tests/dependency-injection-for-constructors-and-methods.html

public String testName;
@BeforeEach
public void setup(TestInfo testInfo) {
Optional<Method> testMethod = testInfo.getTestMethod();
if (testMethod.isPresent()) {
this.testName = testMethod.get().getName();
}
}

修正前に@Beforeメソッドが存在している場合は、引数にTestInfoを取るように修正され、テストメソッド名を取得する処理が追加されます。複数の@Beforeメソッドが存在している場合は、最後に記載されている@Beforeメソッドに処理が追加されるため、必要に応じて、修正されたテストメソッド名の取得処理を他の@BeforeEachメソッドに移動する必要があります。

JUnit4

@Rule
public TestName testName = new TestName();
@Before
public final void setUp1(){
// 事前処理
}
@Before
public final void setUp2(){
// 事前処理
}

JUnit5

public String testName;
@BeforeEach
public void setUp1() {
// 事前処理
}
@BeforeEach
public void setUp2(TestInfo testInfo) {
Optional<Method> testMethod = testInfo.getTestMethod();
if (testMethod.isPresent()) {
this.testName = testMethod.get().getName();
}
// 事前処理
}

3.7. Mockito

JUnit4
JUnit4では@Ruleを使用してMockitoJUnit.rule()を登録することで、テストごとにMockitoのモックオブジェクトを初期化していました。

public class MyTests {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
}

JUnit5
JUnit5では、@Ruleの代わりに@ExtendWith(MockitoExtension.class)を使用してMockitoの拡張機能を登録することで、同様の機能を実現します。

@ExtendWith(MockitoExtension.class)
public class MyTests {
}

4. 手動で修正が必要な箇所

OpenRewriteを使用しても自動で移行されない箇所は以下の通りです。

これらの修正はJUnit公式ドキュメントのMigrating from JUnit 4の内容を確認しつつ、GitHub Copilotや置換を活用して修正を行いました。なお、ここに記載している一例は私が実際に修正した内容を基にしているため、他にも修正が必要な箇所がある可能性があります。

4.1. Hamcrestの追加

JUnit4には依存関係としてHamcrestが含まれていましたが、JUnit5では含まれていません。Hamcrestを使用していた場合は手動で追加する必要があります。

<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>

4.2. Mockitoの依存関係

JUnit4でMockitoを使う場合はmockito-coreが必要でしたが、JUnit5の場合はmockito-junit-jupiterが必要となります。3.7. MockitoMockitoExtension.classを使用する場合は、手動で追加する必要があります。

JUnit5

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

4.3. パラメーター化テスト

同じテストメソッドを複数のデータで繰り返し実行する場合のパラメーター化テストは、手動で修正する必要があります。ここにある例は一例であるため、実際のコードとJUnit5のドキュメントを参考に修正が必要です。

JUnit4
@RunWith(Theories.class)をテストクラスに付与し、@DataPointsを使用してデータポイントを定義します。対象となるテストには@Theoryを付与します。

@RunWith(Theories.class)
public class MyParameterizedTest {
@DataPoints
public static Object[] dataPoints = { "data1", "data2" , "data3" };
@Theory
public void testWithDataPoints(String data) {
// テストコード
}
}

JUnit5
JUnit5では、Stream APIなどを使用してデータポイントを提供します。対象となるテストには@ParameterizedTestを付与し、@MethodSourceにデータポイントを提供するメソッド名を指定します。
https://docs.junit.org/current/writing-tests/parameterized-classes-and-tests.html

public class MyParameterizedTest {
// データポイントを提供するメソッド
public static Stream<String> dataPoints() {
return Stream.of("data1", "data2", "data3");
}
@ParameterizedTest
@MethodSource("dataPoints")
public void testWithDataPoints(String data) {
// テストコード
}
}

4.4. TestWatcher

テストの実行結果を監視するためにTestWatcherを使用している場合、JUnit4とJUnit5での実装方法が異なります。

JUnit4
JUnit4では@Ruleを使用してTestWatcherを登録して、テストの実行結果(成功・失敗など)を監視し、任意の処理をフックします。

public class FunctionTestSupport {
@Rule
public TestWatcher testWatcher = new TestWatcher() {
@Override
protected void succeeded(Description description) {
// テスト成功時の処理
}
@Override
protected void failed(Throwable e, Description description) {
// テスト失敗時の処理
}
};
}

JUnit5
JUnit5では@Ruleが廃止され、代わりにTestWatcherインターフェースを実装したクラスを作成し、テストクラスに登録する必要があります。

public class FunctionTestSupportExtension implements TestWatcher {
@Override
public void testSuccessful(ExtensionContext context) {
// テスト成功時の処理
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
// テスト失敗時の処理
}
}

作ったクラスは以下の2つの方法でテストクラスに登録することができます。

  • @ExtendWithを使用する
  • @RegisterExtensionを使用する

@RegisterExtensionを使用する場合は、作成したクラスのインスタンスに引数を渡すことができます。

  • @ExtendWithを使用する
@ExtendWith(FunctionTestSupportExtension.class)
public class FunctionTestSupport {
// テストメソッド
}
  • @RegisterExtensionを使用する
public class FunctionTestSupport {
@RegisterExtension
public FunctionTestSupportExtension functionTestSupport = new FunctionTestSupportExtension(this);
// テストメソッド
}

5. まとめ

この記事では、OpenRewriteを使用したJUnit4からJUnit5への移行方法と手動で修正が必要な箇所について説明しました。私の体感では、手動での修正コストを70%ほど削減できたように感じました。移行作業を効率化するために、OpenRewriteを使用しつつ公式ドキュメントを参考にしながら手動での修正を行うことをお勧めします。

6. 参考

記事の執筆にあたっては情報の正確性に努めておりますが、掲載されている文章やソースコード、設定ファイル等の内容について、完全な正確性や安全性を保証するものではありません。活用される際は、必ず公式ドキュメント等をご自身で確認のうえご判断ください。


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。