1. ホーム
  2. java

[解決済み] モッキートで発見された未完成のスタッブ

2022-01-30 22:41:23

質問

テストの実行中に以下のような例外が発生します。モッキングにMockitoを使用しています。Mockitoのライブラリに記載されているヒントは役に立ちません。

org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
    -> at com.a.b.DomainTestFactory.myTest(DomainTestFactory.java:355)

    E.g. thenReturn() may be missing.
    Examples of correct stubbing:
        when(mock.isOk()).thenReturn(true);
        when(mock.isOk()).thenThrow(exception);
        doThrow(exception).when(mock).someVoidMethod();
    Hints:
     1. missing thenReturn()
     2. you are trying to stub a final method, you naughty developer!

        at a.b.DomainTestFactory.myTest(DomainTestFactory.java:276)
        ..........

からのテストコード DomainTestFactory . 以下のテストを実行すると、例外が表示されます。

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    Mockito.when(mainModel.getList()).thenReturn(getSomeList()); // Line 355
}

private List<SomeModel> getSomeList() {
    SomeModel model = Mockito.mock(SomeModel.class);
    Mockito.when(model.getName()).thenReturn("SomeName"); // Line 276
    Mockito.when(model.getAddress()).thenReturn("Address");
    return Arrays.asList(model);
}

public class SomeModel extends SomeInputModel{
    protected String address;
    protected List<SomeClass> properties;

    public SomeModel() {
        this.Properties = new java.util.ArrayList<SomeClass>(); 
    }

    public String getAddress() {
        return this.address;
    }

}

public class SomeInputModel{

    public NetworkInputModel() {
        this.Properties = new java.util.ArrayList<SomeClass>(); 
    }

    protected String Name;
    protected List<SomeClass> properties;

    public String getName() {
        return this.Name;
    }

    public void setName(String value) {
        this.Name = value;
    }
}

解決方法は?

モッキングの中にモッキングを入れ子にしている。 あなたは getSomeList() に対するモックを完了させる前に、いくつかのモックを実行します。 MyMainModel . このようなことをすると、Mockitoは嫌がります。

置き換える

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    Mockito.when(mainModel.getList()).thenReturn(getSomeList()); --> Line 355
}

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    List<SomeModel> someModelList = getSomeList();
    Mockito.when(mainModel.getList()).thenReturn(someModelList);
}

この問題を理解するためには、Mockitoの仕組みと、Javaで式や文がどのような順序で評価されるかを知っておく必要があります。

Mockitoはソース・コードを読むことができないので、何をするように要求されているかを理解するために、静的状態に大きく依存します。 モックオブジェクトのメソッドを呼び出すと、Mockitoはその呼び出しの詳細を内部の呼び出しリストに記録します。 その際 when メソッドは、これらの呼び出しのうち最後のものをリストから読み出し、この呼び出しを OngoingStubbing オブジェクトを返します。

行の

Mockito.when(mainModel.getList()).thenReturn(someModelList);

は、Mockitoと次のようなやりとりをします。

  • モックメソッド mainModel.getList() が呼び出されます。
  • 静的メソッド when が呼び出されます。
  • 方法 thenReturn が呼び出されます。 OngoingStubbing オブジェクトを返します。 when メソッドを使用します。

は、その thenReturn メソッドで受け取ったモックを OngoingStubbing メソッドへの任意の適切な呼び出しを処理するために getList を返すメソッドです。 someModelList .

実は、Mockitoはあなたのコードを見ることができないので、以下のようにモッキングを書くことも可能です。

mainModel.getList();
Mockito.when((List<SomeModel>)null).thenReturn(someModelList);

このスタイルでは、特にこの場合は null をキャストする必要がありますが、これはMockitoとの一連のやりとりを生成し、上の行と同じ結果を達成します。

しかし、この行は

Mockito.when(mainModel.getList()).thenReturn(getSomeList());

は、Mockitoと次のようなやりとりをします。

  1. モックメソッド mainModel.getList() が呼び出されます。
  2. 静的メソッド when が呼び出されます。
  3. 新しい mockSomeModel が作成されます(内部 getSomeList() ),
  4. モックメソッド model.getName() が呼び出されます。

この時点でMockitoは混乱します。 あなたがモックしているのは mainModel.getList() をモック化するように言っているのです。 model.getName() メソッドを使用します。 Mockitoには、以下のように見えます。

when(mainModel.getList());
// ...
when(model.getName()).thenReturn(...);

にはバカバカしく見える。 Mockito で何をしているのかがわからないからです。 mainModel.getList() .

に至っていないことに注意してください。 thenReturn というのも、JVMはメソッドを呼び出す前に、このメソッドへのパラメータを評価する必要があるからです。 この場合、これは getSomeList() メソッドを使用します。

一般に、Mockitoのように静的状態に依存するのは設計上よくない判断です。なぜなら、最小驚嘆の原則に違反するケースにつながるからです。 しかし、Mockitoの設計は、時には驚きをもたらすとしても、明確で表現力豊かなモッキングを可能にします。

最後に、最近のMockitoのバージョンでは、上記のエラーメッセージに1行追加されています。 この追加行は、この質問と同じ状況になっている可能性を示しています。

3: 'thenReturn' 命令が完了する前に、別のモックの挙動をスタブしています。