1. 序章

これまで、JMockitMockitoについて幅広く話し合ってきました。

このチュートリアルでは、別のモックツールEasyMockを紹介します。

2. Mavenの依存関係

飛び込む前に、pom.xmlに次の依存関係を追加しましょう。

<dependency>
    <groupId>org.easymock</groupId>
    <artifactId>easymock</artifactId>
    <version>3.5.1</version>
    <scope>test</scope>
</dependency>

最新バージョンはいつでもここで見つけることができます。

3. コアコンセプト

モックを生成するとき、ターゲットオブジェクトをシミュレートし、その動作を指定し、最後にそれが期待どおりに使用されているかどうかを確認できます。

EasyMockのモックの操作には、次の4つのステップが含まれます。

  1. ターゲットクラスのモックを作成する
  2. アクション、結果、例外など、予想される動作を記録します。
  3. テストでモックを使用する
  4. 期待どおりに動作しているかどうかを確認する

記録が終了したら、「再生」モードに切り替えます。これにより、モックを使用するオブジェクトとコラボレーションするときに、モックが記録されたとおりに動作します。

最終的に、すべてが期待どおりに行われるかどうかを確認します。

上記の4つのステップは、org.easymock.EasyMockのメソッドに関連しています。

  1. mock(…):具象クラスであれインターフェースであれ、ターゲットクラスのモックを生成します。 作成されると、モックは「記録」モードになります。つまり、EasyMockは、モックオブジェクトが実行するすべてのアクションを記録し、「再生」モードで再生します。
  2. expected(…):このメソッドを使用すると、関連する記録アクションに対して、呼び出し、結果、例外などの期待値を設定できます。
  3. replay(…):指定されたモックを「リプレイ」モードに切り替えます。 次に、以前に記録されたメソッド呼び出しをトリガーするアクションは、「記録された結果」を再生します
  4. verify(…):すべての期待が満たされ、モックで予期しない呼び出しが実行されなかったことを検証します

次のセクションでは、実際の例を使用して、これらの手順が実際にどのように機能するかを示します。

4. モッキングの実例

先に進む前に、コンテキストの例を見てみましょう。たとえば、Webサイトの記事を閲覧するのが好きなBaeldungブログの読者がいて、彼/彼女が記事を書き込もうとしているとします。

次のモデルを作成することから始めましょう。

public class BaeldungReader {

    private ArticleReader articleReader;
    private IArticleWriter articleWriter;

    // constructors

    public BaeldungArticle readNext(){
        return articleReader.next();
    }

    public List<BaeldungArticle> readTopic(String topic){
        return articleReader.ofTopic(topic);
    }

    public String write(String title, String content){
        return articleWriter.write(title, content);
    }
}

このモデルには、 articleReader (具象クラス)と articleWriter (インターフェース)の2つのプライベートメンバーがあります。

次に、それらをモックしてBaeldungReaderの動作を確認します。

5. Javaコードでモック

ArticleReaderをモックすることから始めましょう。

5.1. 典型的なモッキング

リーダーが記事をスキップすると、 articleReader.next()メソッドが呼び出されることが期待されます。

@Test
public void whenReadNext_thenNextArticleRead(){
    ArticleReader mockArticleReader = mock(ArticleReader.class);
    BaeldungReader baeldungReader
      = new BaeldungReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    replay(mockArticleReader);

    baeldungReader.readNext();

    verify(mockArticleReader);
}

上記のサンプルコードでは、4ステップの手順に厳密に準拠し、ArticleReaderクラスをモックします。

mockArticleReader.next()が何を返すかは実際には気にしませんが、 expected(…)。andReturn(…)を使用してmockArticleReader.next()の戻り値を指定する必要があります。

expected(…)を使用すると、EasyMockは、メソッドが値を返すか、Exceptionをスローすることを期待しています。

単純に行う場合:

mockArticleReader.next();
replay(mockArticleReader);

EasyMockは、メソッドが何かを返す場合に expected(…)。andReturn(…)を呼び出す必要があるため、これについて文句を言います。

void メソッドの場合、次のように expectedLastCall()を使用してそのアクションをexpectedできます。

mockArticleReader.someVoidMethod();
expectLastCall();
replay(mockArticleReader);

5.2. リプレイオーダー

アクションを特定の順序で再生する必要がある場合は、より厳密にすることができます。

@Test
public void whenReadNextAndSkimTopics_thenAllAllowed(){
    ArticleReader mockArticleReader
      = strictMock(ArticleReader.class);
    BaeldungReade baeldungReader
      = new BaeldungReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    expect(mockArticleReader.ofTopic("easymock")).andReturn(null);
    replay(mockArticleReader);

    baeldungReader.readNext();
    baeldungReader.readTopic("easymock");

    verify(mockArticleReader);
}

このスニペットでは、 strictMock(…)を使用してメソッド呼び出しの順序を確認します mock(…)および strictMock(…)によって作成されたモックの場合、予期しないメソッド呼び出しによりAssertionErrorが発生します。

モックのメソッド呼び出しを許可するには、 niceMock(…)を使用できます。

@Test
public void whenReadNextAndOthers_thenAllowed(){
    ArticleReader mockArticleReader = niceMock(ArticleReader.class);
    BaeldungReade baeldungReader = new BaeldungReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    replay(mockArticleReader);

    baeldungReader.readNext();
    baeldungReader.readTopic("easymock");

    verify(mockArticleReader);
}

ここでは、 baeldungReader.readTopic(…)が呼び出されるとは予想していませんでしたが、EasyMockは文句を言いません。 niceMock(…)を使用すると、 EasyMockは、ターゲットオブジェクトが期待されるアクションを実行したかどうかのみを処理するようになりました。

5.3. 例外スローをあざける

それでは、インターフェース IArticleWriter のモックと、予想されるThrowablesの処理方法を続けましょう。

@Test
public void whenWriteMaliciousContent_thenArgumentIllegal() {
    // mocking and initialization

    expect(mockArticleWriter
      .write("easymock","<body onload=alert('baeldung')>"))
      .andThrow(new IllegalArgumentException());
    replay(mockArticleWriter);

    // write malicious content and capture exception as expectedException

    verify(mockArticleWriter);
    assertEquals(
      IllegalArgumentException.class, 
      expectedException.getClass());
}

上記のスニペットでは、 articleWriter は、 XSS(クロスサイトスクリプティング)攻撃を検出するのに十分な堅牢性を備えていると予想されます。

したがって、リーダーが悪意のあるコードを記事のコンテンツに挿入しようとすると、ライターはIllegalArgumentExceptionをスローする必要があります。 expected(…)。andThrow(…)を使用して、この予想される動作を記録しました。

6. 注釈付きのモック

EasyMockは、注釈を使用したモックの挿入もサポートしています。 それらを使用するには、EasyMockRunnerを使用して単体テストを実行し、@Mockおよび@TestSubjectアノテーションを処理する必要があります。

以前のスニペットを書き直してみましょう。

@RunWith(EasyMockRunner.class)
public class BaeldungReaderAnnotatedTest {

    @Mock
    ArticleReader mockArticleReader;

    @TestSubject
    BaeldungReader baeldungReader = new BaeldungReader();

    @Test
    public void whenReadNext_thenNextArticleRead() {
        expect(mockArticleReader.next()).andReturn(null);
        replay(mockArticleReader);
        baeldungReader.readNext();
        verify(mockArticleReader);
    }
}

mock(…)と同等で、@Mockで注釈が付けられたフィールドにモックが挿入されます。 そして、これらのモックは、@TestSubjectで注釈が付けられたクラスのフィールドに挿入されます。

上記のスニペットでは、baeldungReaderのarticleReaderフィールドを明示的に初期化していません。baeldungReader.readNext()を呼び出すと、暗黙的にmockArticleReader

これは、mockArticleReaderarticleReaderフィールドに挿入されたためです。

EasyMockRunner の代わりに別のテストランナーを使用する場合は、JUnitテストルールEasyMockRuleを使用できることに注意してください。

public class BaeldungReaderAnnotatedWithRuleTest {

    @Rule
    public EasyMockRule mockRule = new EasyMockRule(this);

    //...

    @Test
    public void whenReadNext_thenNextArticleRead(){
        expect(mockArticleReader.next()).andReturn(null);
        replay(mockArticleReader);
        baeldungReader.readNext();
        verify(mockArticleReader);
    }

}

7. EasyMockSupportでモック

1回のテストで複数のモックを導入する必要があり、手動で繰り返す必要がある場合があります。

replay(A);
replay(B);
replay(C);
//...
verify(A);
verify(B);
verify(C);

これは醜いので、エレガントなソリューションが必要です。

幸い、EasyMockにクラスEasyMockSupportがあり、これに対処するのに役立ちます。 はモックを追跡するのに役立ち、次のようなバッチでモックを再生して確認できます。

//...
public class BaeldungReaderMockSupportTest extends EasyMockSupport{

    //...

    @Test
    public void whenReadAndWriteSequencially_thenWorks(){
        expect(mockArticleReader.next()).andReturn(null)
          .times(2).andThrow(new NoSuchElementException());
        expect(mockArticleWriter.write("title", "content"))
          .andReturn("BAEL-201801");
        replayAll();

        // execute read and write operations consecutively
 
        verifyAll();
 
        assertEquals(
          NoSuchElementException.class, 
          expectedException.getClass());
        assertEquals("BAEL-201801", articleId);
    }

}

ここでは、articleReaderarticleWriterの両方をモックしました。 これらのモックを「再生」モードに設定する場合、 EasyMockSupportが提供する静的メソッドreplayAll()を使用し、 verifyAll()を使用してバッチ。

また、 expectedフェーズでtimes(…)メソッドを導入しました。 重複コードの導入を回避できるように、メソッドが呼び出されると予想される回数を指定するのに役立ちます。

委任を通じてEasyMockSupportを使用することもできます。

EasyMockSupport easyMockSupport = new EasyMockSupport();

@Test
public void whenReadAndWriteSequencially_thenWorks(){
    ArticleReader mockArticleReader = easyMockSupport
      .createMock(ArticleReader.class);
    IArticleWriter mockArticleWriter = easyMockSupport
      .createMock(IArticleWriter.class);
    BaeldungReader baeldungReader = new BaeldungReader(
      mockArticleReader, mockArticleWriter);

    expect(mockArticleReader.next()).andReturn(null);
    expect(mockArticleWriter.write("title", "content"))
      .andReturn("");
    easyMockSupport.replayAll();

    baeldungReader.readNext();
    baeldungReader.write("title", "content");

    easyMockSupport.verifyAll();
}

以前は、静的メソッドまたはアノテーションを使用してモックを作成および管理していました。 内部的には、これらの静的および注釈付きモックは、グローバルEasyMockSupportインスタンスによって制御されます。

ここでは、明示的にインスタンス化し、委任を通じて、これらすべてのモックを独自の制御下に置きます。 これは、EasyMockとのテストコードに名前の競合がある場合、または同様のケースがある場合に、混乱を避けるのに役立つ可能性があります。

8. 結論

この記事では、EasyMockの基本的な使用法、モックオブジェクトの生成方法、それらの動作の記録と再生方法、およびそれらが正しく動作したかどうかの検証方法について簡単に紹介しました。

興味のある方は、EasyMock、Mocket、JMockitの比較についてこの記事をチェックしてください。

いつものように、完全な実装はGithubにあります。