自動化

CDI-Unitとは?Java CDIのBeanをJUnit 5でテストする設定とweld-junitとの選び方

CDI-Unitとは?Java CDIのBeanをJUnit 5でテストする設定とweld-junitとの選び方

CDI-Unitは、テストのたびにWeld SEを起動し、Java CDI(Jakarta Contexts and Dependency Injection)のBeanへ本番と同じ仕組みで注入させる単体テスト用ライブラリです。アプリケーションサーバーを立てずに、@Injectやalternative、スコープを含めた動作を確かめられます。なお「CDI」は業種によって別の略語にも使われますが、本記事ではJavaの依存性注入仕様を指します。

2026年9月15日時点で、公開されている最新版は安定版の4.4.0(javax向け)と、jakarta向けの5.0.0-EA7(Early Access)です。どちらを入れるか、そもそもCDI-Unitを選ぶべきかで判断が分かれるため、版の違い・Maven設定・JUnit 5での書き方を実行結果とあわせて整理します。

まとめ:CDI-Unitの版の選び方と使いどころ

  • javax名前空間(CDI 1.x/2.x)のプロジェクトは、安定版のio.github.cdi-unit:cdi-unit:4.4.0を使います。4系は機能追加もバックポートもされません。
  • jakarta名前空間(CDI 3.x以降)に対応するのは5系で、公開済みの最新Early Access版は5.0.0-EA7です。2024年9月29日の公開から約2年、Final版は出ていません。
  • CDI-UnitはWeldを依存に含めません。weld-se-coreを自分で追加しないと起動しません。
  • JUnit 5では@ExtendWith(CdiJUnit5Extension.class)を付けます。後述の例では、自動検出されない実装クラスを@AdditionalClassesで追加し、指定しないとWELD-001408で落ちます。
  • 新規のjakartaプロジェクトなら、Final版があるweld-junit(weld-junit5 5.0.3.Final)を先に検討します。実サーバー上のJPAやトランザクションまで確かめたい結合テストはArquillianの領分です。

Java CDIとCDI-Unitの関係

CDIはJakarta EEの仕様の一つで、Beanの生成・注入・スコープ管理・イベント・インターセプターの振る舞いを定めています。仕様の現行版はJakarta EE 11に含まれるCDI 4.1で、Jakarta EE 12向けのCDI 5.0は開発中です(jakarta.eeの仕様ページ)。仕様を実装するコンテナの一つがWeldで、Java SE上で起動できる形態がWeld SEです。

Springのコンテナも依存性注入を行いますが、CDIとは別系統の仕組みで、アノテーションや設定の流儀が異なります。Spring側の注入方式はSpringのDI(依存性注入)とは?仕組みと3つの注入方法をわかりやすく解説で扱っています。

CDIのBeanをMockitoだけでテストすると、フィールド注入やalternativeの選択、@RequestScopedのようなスコープはテストコード側で再現するしかありません。CDI-Unitは、テストクラスを起点に必要なクラスをWeld SEへデプロイし、テストクラス自身にも@Injectで注入します。コンテナが解決するので、注入漏れや曖昧な依存はテストの時点でデプロイエラーとして現れます。

CDI-Unit 4.4.0と5.0.0-EA7の対応範囲

項目 4.4.0 5.0.0-EA7
公開日 2024-07-07 2024-09-29
位置付け 安定版 Early Access
名前空間 javax(CDI 1.x/2.x) jakarta(CDI 3.x以降)
Java 8/11 11以上
POM上のWeld 3.1.9.Final(provided) 4.0.3.Final(provided)
テストランナー JUnit 4/TestNG JUnit 4/JUnit 5/TestNG/Spock

CDI-UnitのGitHubリポジトリのREADMEには「一度にサポートするメジャー版は一つだけで、変更は旧版へバックポートしない」と明記されています。GitHubのmasterブランチは5.0.0-SNAPSHOTのまま2026年9月も依存ライブラリの更新が続いており、開発は5系で進んでいます。

groupIdとパッケージ名の移行

4.3でgroupIdがorg.jglue.cdi-unitからio.github.cdi-unitへ移りました。Maven Centralのjarを確認すると、パッケージも4.2.0までがorg.jglue.cdiunit、4.3.0以降がio.github.cdiunitです。旧座標の扱いは版によって違います。

  • org.jglue.cdi-unit:cdi-unit:5.0.0-EA7はio.github側へ転送するrelocation POMです。
  • org.jglue.cdi-unit:cdi-unit:4.4.0はpackagingがpomで、jarが置かれていません。4.4.0は必ずio.github.cdi-unitで指定します。

4.2.0以前から上げる場合は、依存の座標に加えてimport文のorg.jglue.cdiunitも書き換える必要があります。

5.0.0-EA7で増えたテストランナー

5.0.0-EA7のリリースノートには、JUnit 5対応(Nestedテストを含む)、TestNG・Spock対応のモジュール分割、Jakarta EE対応、Weld 5.1.3.Final対応が並んでいます。集約アーティファクトcdi-unitを指定するとcdi-unit-junit5やcdi-unit-testngもまとめて入ります。JUnit 5用の拡張はio.github.cdiunit.junit5.CdiJUnit5Extensionです。

Mavenの依存関係設定

検証用プロジェクトでは、次の依存関係に加えてmaven.compiler.releaseを17、maven-surefire-pluginを3.5.2に指定し、mvn testで後述のテストを実行しました。CDI-Unit本体のPOMではWeldとMockitoがprovidedスコープなので、テスト側で明示します。

<dependency>
  <groupId>jakarta.enterprise</groupId>
  <artifactId>jakarta.enterprise.cdi-api</artifactId>
  <version>4.0.1</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>io.github.cdi-unit</groupId>
  <artifactId>cdi-unit</artifactId>
  <version>5.0.0-EA7</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.jboss.weld.se</groupId>
  <artifactId>weld-se-core</artifactId>
  <version>5.1.3.Final</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.11.1</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>5.23.0</version>
  <scope>test</scope>
</dependency>

javaxのプロジェクトで4.4.0を使う場合は、座標をio.github.cdi-unit:cdi-unit:4.4.0にし、Weld SEも3.x系(4.4.0のPOMは3.1.9.Final)を合わせます。JUnitは4系です。Gradleでも座標は同じです。CDI APIは本番コードのコンパイル用にcompileOnlyへ、テスト用にもtestImplementationへ追加し、CDI-Unit・Weld・JUnit・MockitoはtestImplementationへ指定します。

JUnit 5でのテストの書き方

ここからの例は、Engineインターフェースを@Injectで受け取るStarshipクラスと、その実装WarpDriveを題材にしています(CDI-Unit公式READMEの例と同じ構成)。以下の3型はそれぞれEngine.java、WarpDrive.java、Starship.javaに保存し、Starship.javaにはimport jakarta.inject.Inject;を追加します。各型と後述のテストは同じパッケージに配置します。

public interface Engine {
    String start();
}

public class WarpDrive implements Engine {
    @Override
    public String start() {
        return "warp";
    }
}

public class Starship {
    @Inject
    Engine engine;

    public String start() {
        return engine.start();
    }
}

@AdditionalClassesによる実装クラスの追加

CDI-Unitがデプロイするのは、テストクラスから辿れるクラスが中心です。インターフェースの実装はテストクラスから見えないため、@AdditionalClassesで明示します。

import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import io.github.cdiunit.AdditionalClasses;
import io.github.cdiunit.junit5.CdiJUnit5Extension;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(CdiJUnit5Extension.class)
@AdditionalClasses(WarpDrive.class)
class StarshipTest {

    @Inject
    Starship starship;

    @Test
    void startsWithWarpDrive() {
        assertEquals("warp", starship.start());
    }
}

このテストは成功します。@AdditionalClassesの行だけを外すと、テストインスタンスの生成時点で次のエラーになりました。

WELD-001408: Unsatisfied dependencies for type Engine with qualifiers @Default

この例は、インターフェースの実装を自動検出できない場合の失敗を示しています。WELD-001408が出たら、まず注入先の型の実装がデプロイに含まれているかを疑います。同じパッケージのクラスをまとめて入れるなら@AdditionalPackagesが使えます。

@Produces・@Mock・@ProducesAlternativeによるモックへの差し替え

テストクラスのフィールドに@ProducesとMockitoの@Mockを併記すると、そのモックがCDIのBeanとして供給されます。本物の実装も同時にデプロイされている場合は、@ProducesAlternativeを付けてモックを優先させます。

import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import io.github.cdiunit.AdditionalClasses;
import io.github.cdiunit.ProducesAlternative;
import io.github.cdiunit.junit5.CdiJUnit5Extension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(CdiJUnit5Extension.class)
@AdditionalClasses(WarpDrive.class)
class StarshipMockTest {

    @Produces
    @ProducesAlternative
    @Mock
    Engine engine;

    @Inject
    Starship starship;

    @Test
    void mockReplacesWarpDrive() {
        when(engine.start()).thenReturn("mock");
        assertEquals("mock", starship.start());
        verify(engine).start();
    }
}

WarpDriveがデプロイされていても、Starshipにはモックが注入され、"mock"が返ります。モックとスタブの使い分けそのものはモックとは?スタブとの違い・テストダブル5分類と単体テストでの使い分けを実装者向けに解説を参照してください。

JUnit 4とTestNGでのランナー指定

JUnit 4では@RunWith(CdiRunner.class)を付けます。CdiRunnerの完全修飾名は版で異なり、4.2.0まではorg.jglue.cdiunit.CdiRunner、4.3.0以降はio.github.cdiunit.CdiRunnerです。5.0.0-EA7ではモジュール分割に合わせてio.github.cdiunit.junit4.CdiRunnerも追加されています。

TestNGは版で方式が違います。4.4.0のjarにあるのは継承して使うNgCdiRunnerだけで、現行の公式ガイドが示す@Listeners(NgCdiListener.class)は5.0.0-EA7のjarで提供されています。JUnit自体の基本はJUnitとは?Javaの単体テストフレームワークの基本と書き方【2026年版】で扱っています。

デプロイとスコープを制御するアノテーション

アノテーション 公式ガイドでの役割
@AdditionalClasses 指定クラスをデプロイに追加
@AdditionalPackages 指定クラスと同じパッケージを追加
@AdditionalClasspaths 同じクラスパスエントリのクラスを追加
@IgnoredClasses 指定クラスをデプロイから除外
@ActivatedAlternatives alternativeを検出対象にして有効化
@ProducesAlternative モックを他の実装より優先
@InRequestScope テストメソッド中にrequestスコープを開始
@InSessionScope テストメソッド中にsessionスコープを開始
@InConversationScope テストメソッド中にconversationスコープを開始

@RequestScopedや@SessionScopedのBeanを使うテストでは、テストクラスかメソッドに@InRequestScopeなどを付けてスコープを開始します。EJBを注入する@SupportEjb、JAX-RSのクラスを使える@SupportJaxRsも用意されています。

実行して分かったCDI-Unit 5.0.0-EA7の注意点

2026年9月15日に、macOS上のOpenJDK 26.0.2とMaven 3.9.16で前述のテストを実行しました。公式ドキュメントからは読み取れない差が2つ出ています。

組み合わせ 結果
Weld SE 5.1.3.Final+Mockito 5.14.0 モックのテストだけ失敗
Weld SE 5.1.3.Final+Mockito 5.23.0 全テスト成功
Weld SE 6.0.3.Final+Mockito 5.23.0 全テスト成功

Mockito 5.14.0とJDK 26の組み合わせでの初期化失敗

5.0.0-EA7のPOMが参照しているMockitoは5.14.0です。これをそのまま使うと、JDK 26ではCould not initialize plugin: interface org.mockito.plugins.MockMakerに続いてUnknown Java version: 0が出てモックを作れませんでした。記録した環境ではMockitoを5.23.0に上げて成功しました。ただし、Java 21以降ではエージェントの動的接続が制限される場合があります。接続に失敗する環境では、テストJVMの-javaagent引数にMockitoのjarを指定します。CDI-Unit側ではなく、テストを動かすJDKにMockitoの版を合わせる必要があります。

Weld SE 6.0.3.Finalでの注入・モック差し替えの検証結果

リリースノートで明示されている対応はWeld 5.1.3.Finalまでですが、Weld SE 6.0.3.Finalに差し替えても上記2本のテストは成功しました。ただし確認したのは注入とモック差し替えだけです。スコープやインターセプターを多用するテストでは、Weld 6.0へ上げる前に既存テストを一通り流して判断します。

weld-junit・Arquillianとの選び分け

CDIのBeanをテストする道具はCDI-Unitだけではありません。Weldプロジェクト自身がweld-testing(JUnit 4/JUnit 5/Spock向け拡張)を公開しており、統合テストの側にはArquillianがあります。

項目 CDI-Unit weld-junit5 Arquillian
最新版 4.4.0/5.0.0-EA7 5.0.3.Final 1.10.2.Final
公開日 2024-07-07/2024-09-29 2025-10-08 2026-05-20
対象 javax/jakarta Weld 6.0(CDI 4.1) 実コンテナ
起動するもの Weld SE Weld SE コンテナへデプロイ
主な用途 単体テスト 単体テスト 統合テスト

weld-junit5では、@EnableWeldを付けてWeldInitiatorでデプロイするクラスを指定します。前述のStarshipをモック注入でテストすると次の形になり、weld-junit5 5.0.3.Final(Weld 6.0.3.Final)で成功しました。

import jakarta.inject.Inject;
import org.jboss.weld.junit.MockBean;
import org.jboss.weld.junit5.EnableWeld;
import org.jboss.weld.junit5.WeldInitiator;
import org.jboss.weld.junit5.WeldSetup;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

@EnableWeld
class StarshipWeldTest {

    @WeldSetup
    WeldInitiator weld = WeldInitiator.from(Starship.class)
            .addBeans(MockBean.builder()
                    .types(Engine.class)
                    .creating((Engine) () -> "mock")
                    .build())
            .build();

    @Inject
    Starship starship;

    @Test
    void mockBeanIsInjected() {
        assertEquals("mock", starship.start());
    }
}

weld-junit5のREADMEが挙げる要件はJUnit 5とJava 17です。2026年6月12日公開の6.0.0.Beta1ではWeld 7.0(CDI 5.0)対応に合わせてアーティファクト名がweld-junit-jupiterに、パッケージがorg.jboss.weld.junit.jupiterに変わりました。移行用にOpenRewriteのレシピが提供されています。

選び方は次のとおりです。

  • 既存のjavaxプロジェクトでCDI-Unit 4系のテストが大量にある:4.4.0に揃えて維持します。jakartaへ移行するときに、@AdditionalClassesなどのアノテーション体系をそのまま持ち込めるのが5.0.0-EA7の価値です。
  • 新規のjakartaプロジェクト:weld-junit5 5.0.3.Finalを選びます。CDI-Unit 5はFinal版が無く、CIの基盤に据えるには版の固定と上げ時の検証を自分で抱えることになります。
  • JPA・JTA・EJBなど実サーバーの機能ごと確かめたい:Arquillianでコンテナへデプロイします。前述の実行でもWeld SEは起動時にWELD-000101: Transactional services not availableを出しており、トランザクションに依存する振る舞いはCDI-Unitの守備範囲外です。

単体テストと統合テストの本数配分は、テストピラミッドとは?単体・結合・E2Eの配分と実装者向けテスト戦略設計の考え方で決めると、CDI-UnitとArquillianの役割が重なりません。

よくある質問

JavaのCDIとは何ですか?

Jakarta EEの仕様であるJakarta Contexts and Dependency Injectionの略で、Beanの注入・スコープ・イベントなどを定めています。現行版はJakarta EE 11に含まれるCDI 4.1で、実装にはWeldなどがあります。

CDI-UnitはJUnit 5で使えますか?

5.0.0-EA7で使えます。テストクラスに@ExtendWith(io.github.cdiunit.junit5.CdiJUnit5Extension.class)を付けます。安定版の4.4.0はJUnit 4とTestNGのみです。

CDI-Unitを入れればWeldも自動で入りますか?

入りません。CDI-UnitのPOMではWeldがprovidedスコープのため、org.jboss.weld.se:weld-se-coreをテストスコープで追加します。4.4.0なら3.x系、5.0.0-EA7ならWeld 5.1.3.Finalが公式に対応を明記している版です。

org.jglue.cdi-unitとio.github.cdi-unitはどちらを指定しますか?

io.github.cdi-unitを指定します。4.3でgroupIdが移っており、org.jglue側の4.4.0にはjarがありません。4.2.0以前から上げる場合はimport文のorg.jglue.cdiunitもio.github.cdiunitへ書き換えます。

WELD-001408が出たときはどこを見ればよいですか?

注入先の型と修飾子(qualifier)を満たす有効なBeanが見つからない状態です。実装の追加漏れに加え、修飾子の不一致やalternativeの有効化漏れも確認します。インターフェースの実装クラスを@AdditionalClassesで追加するか、テスト側で@Produces付きのフィールドやメソッドから供給します。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!不正アクセスでサービス停止:再開未定の現状と運営側の停止判断・証拠保全・段階的再開
  2. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:対象確認・パスワード変更と再開時期
  3. 2026.10.14 テックブログ ローソンID不正アクセス:215万件漏えいの対象確認と本物メールの見分け方
  4. 2026.10.24 テックブログ ブラストメール不正アクセス:配信先288件の漏えいと利用企業の報告・通知の手順
  5. 2024.11.05 テックブログ Python 3.13の新機能と3.12との違い|3.13.17の変更点・サポート期限・GIL無効化の実測

RELATED POSTS 関連記事

目次