web-dev-qa-db-ja.com

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;
    }
}
113
Royal Rose

モッキングの内部にモッキングをネストしています。 MyMainModelのモックが完了する前に、モックを行うgetSomeList()を呼び出しています。これを行うと、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が何を要求しているかを把握するために、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. mockの新しいSomeModelが作成されます(getSomeList()内)、
  4. モックメソッドmodel.getName()が呼び出され、

この時点でMockitoは混乱します。 mainModel.getList()をモックしていると思っていましたが、今ではmodel.getName()メソッドをモックしたいと言っています。 Mockitoにとっては、次のことをしているように見えます。

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

mainModel.getList()で何をしているのかわからないため、これはMockitoにとっては馬鹿げているように見えます。

JVMはメソッドを呼び出す前にこのメソッドのパラメーターを評価する必要があるため、thenReturnメソッド呼び出しに到達しなかったことに注意してください。この場合、これはgetSomeList()メソッドを呼び出すことを意味します。

一般に、Mockitoのように静的状態に依存することは設計上の悪い決定です。これは、Least Astonishmentの原則に違反するケースにつながる可能性があるためです。ただし、Mockitoのデザインは、ときどき驚toにつながる場合でも、明確で表現力豊かなモックを作成します。

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

3:完了したら、「thenReturn」命令の前に別のモックの動作をスタブします

280
Luke Woodward