1. 序章

Fluent APIは、簡潔で読みやすく雄弁なインターフェースを構築するためのメソッドチェーンに基づくソフトウェアエンジニアリング設計手法です。

これらは、ビルダー、工場、その他のクリエイティブデザインパターンによく使用されます。 最近では、 Java の進化とともにますます人気が高まっており、 Java StreamAPIMockitoテストフレームワークなどの人気のあるAPIで見つけることができます。

それでも、モックオブジェクトの複雑な階層を設定する必要があることが多いため、FluentAPIのモックは苦痛を伴う可能性があります

このチュートリアルでは、Mockitoの優れた機能を使用してこれを回避する方法を見ていきます。

2. シンプルなFluentAPI

このチュートリアルでは、ビルダーデザインパターンを使用して、ピザオブジェクトを構築するためのシンプルで流暢なAPIを示します

Pizza pizza = new Pizza
  .PizzaBuilder("Margherita")
  .size(PizzaSize.LARGE)
  .withExtaTopping("Mushroom")
  .withStuffedCrust(false)
  .willCollect(true)
  .applyDiscount(20)
  .build();

ご覧のとおり、 DSL のように読み取り、さまざまな特性を持つPizzaオブジェクトを作成できるわかりやすいAPIを作成しました。

次に、ビルダーを使用する単純なサービスクラスを定義します。 これは、後でテストするクラスになります。

public class PizzaService {

    private Pizza.PizzaBuilder builder;

    public PizzaService(Pizza.PizzaBuilder builder) {
        this.builder = builder;
    }

    public Pizza orderHouseSpecial() {
        return builder.name("Special")
          .size(PizzaSize.LARGE)
          .withExtraTopping("Mushrooms")
          .withStuffedCrust(true)
          .withExtraTopping("Chilli")
          .willCollect(true)
          .applyDiscount(20)
          .build();
    }
}

私たちのサービスは非常にシンプルで、orderHouseSpecialという1つのメソッドが含まれています。 名前が示すように、このメソッドを使用して、いくつかの事前定義されたプロパティを持つ特別なピザを作成できます。

3. 伝統的なモッキング

従来の方法でモックをスタブするには、8つのモックPizzaBuilderオブジェクトを作成する必要があります。 nameメソッドによって返されるPizzaBuilderのモックが必要です。次に、sizeメソッドによって返されるPizzaBuilderのモックが必要です。 、など。 流暢なAPIチェーンのすべてのメソッド呼び出しを満たすまで、この方法を続けます。

次に、従来のMockito mocksを使用してサービスメソッドをテストするための単体テストを作成する方法を見てみましょう。

@Test
public void givenTraditonalMocking_whenServiceInvoked_thenPizzaIsBuilt() {
    PizzaBuilder nameBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
    PizzaBuilder sizeBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
    PizzaBuilder firstToppingBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
    PizzaBuilder secondToppingBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
    PizzaBuilder stuffedBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
    PizzaBuilder willCollectBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
    PizzaBuilder discountBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
        
    PizzaBuilder builder = Mockito.mock(Pizza.PizzaBuilder.class);
    when(builder.name(anyString())).thenReturn(nameBuilder);
    when(nameBuilder.size(any(Pizza.PizzaSize.class))).thenReturn(sizeBuilder);        
    when(sizeBuilder.withExtraTopping(anyString())).thenReturn(firstToppingBuilder);
    when(firstToppingBuilder.withStuffedCrust(anyBoolean())).thenReturn(stuffedBuilder);
    when(stuffedBuilder.withExtraTopping(anyString())).thenReturn(secondToppingBuilder);
    when(secondToppingBuilder.willCollect(anyBoolean())).thenReturn(willCollectBuilder);
    when(willCollectBuilder.applyDiscount(anyInt())).thenReturn(discountBuilder);
    when(discountBuilder.build()).thenReturn(expectedPizza);
                
    PizzaService service = new PizzaService(builder);
    Pizza pizza = service.orderHouseSpecial();
    assertEquals("Expected Pizza", expectedPizza, pizza);

    verify(builder).name(stringCaptor.capture());
    assertEquals("Pizza name: ", "Special", stringCaptor.getValue());

    // rest of test verification
}

この例では、PizzaServiceに提供するPizzaBuilderをモックする必要があります。 ご覧のとおり、モックを返す必要があるため、これは簡単な作業ではありません。モックは、流暢なAPIの呼び出しごとにモックを返します。

これにより、モックオブジェクトの階層が複雑になり、理解が難しく、維持が難しい場合があります。

4. 救助への深いスタブ

ありがたいことに、Mockitoは、モックを作成するときに応答モードを指定できる、ディープスタブと呼ばれる非常に優れた機能を提供します。

深いスタブを作成するには、モックを作成するときに、Mockito.RETURNS_DEEP_STUBS定数を追加の引数として追加するだけです。

@Test
public void givenDeepMocks_whenServiceInvoked_thenPizzaIsBuilt() {
    PizzaBuilder builder = Mockito.mock(Pizza.PizzaBuilder.class, Mockito.RETURNS_DEEP_STUBS);

    Mockito.when(builder.name(anyString())
      .size(any(Pizza.PizzaSize.class))
      .withExtraTopping(anyString())
      .withStuffedCrust(anyBoolean())
      .withExtraTopping(anyString())
      .willCollect(anyBoolean())
      .applyDiscount(anyInt())
      .build())
      .thenReturn(expectedPizza);

    PizzaService service = new PizzaService(builder);
    Pizza pizza = service.orderHouseSpecial();
    assertEquals("Expected Pizza", expectedPizza, pizza);
}

Mockito.RETURNS_DEEP_STUBS 引数を使用して、Mockitoに一種のディープモックを作成するように指示します。 これにより、完全なメソッドチェーンの結果、またはこの場合は流暢なAPIを一度にモックすることができます。

これにより、前のセクションで見たものよりもはるかに洗練されたソリューションと、はるかに理解しやすいテストが実現します。 本質的に、モックオブジェクトの複雑な階層を作成する必要はありません。

この回答モードは、@Mockアノテーションを使用して直接使用することもできます。

@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private PizzaBuilder anotherBuilder;

注意すべき点の1つは、検証はチェーンの最後のモックでのみ機能するということです。

5. 結論

このクイックチュートリアルでは、Mockitoを使用して単純な流暢なAPIをモックする方法を見てきました。 まず、従来のモックのアプローチを見て、この方法に関連する難しさを理解しました。

次に、ディープスタブと呼ばれるMockitoのあまり知られていない機能を使用した例を見てみました。これにより、流暢なAPIをよりエレガントにモックすることができます。

いつものように、記事の完全なソースコードは、GitHubから入手できます。