Mockitoを使用して一部のメソッドのみをモックする


402

Mockitoを使用して、クラスの一部のメソッドだけをモックする方法はありますか?

たとえば、この(確かに考案された)Stockクラスでは、(以下のテストスニペットに示すように)getPrice()getQuantity()戻り値をモックしたいgetValue()が、Stockクラスでコーディングされているとおりに乗算を実行したい

public class Stock {
  private final double price;
  private final int quantity;

  Stock(double price, int quantity) {
    this.price = price;
    this.quantity = quantity;
  }

  public double getPrice() {
    return price;
  }

  public int getQuantity() {
    return quantity;
  }
  public double getValue() {
    return getPrice() * getQuantity();
  }

  @Test
  public void getValueTest() {
    Stock stock = mock(Stock.class);
    when(stock.getPrice()).thenReturn(100.00);
    when(stock.getQuantity()).thenReturn(200);
    double value = stock.getValue();
    // Unfortunately the following assert fails, because the mock Stock getValue() method does not perform the Stock.getValue() calculation code.
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}

4
なぜそうしたいのですか?クラスをテストする必要があります(その場合、モックはまったく必要ありません)、または別のクラスをテストしている間はモックする必要があります(この場合、機能はありません)。なぜ部分モックをするのですか?
weltraumpirat 2013

3
わかりました、これは本物の小さな例です。実際には、不自然な値を渡してデータベースへの呼び出しを回避しようとしていますが、他のメソッドがそれらの不自然な値で正しく機能することを確認したいと思います。これを行うより良い方法はありますか?
Victor Grazi

5
確かに:データベースの呼び出しを別のクラスに移動し(ドメインロジックとデータベースアクセスは同じクラスにあるべきではありません。これらは2つの異なる懸念事項です)、そのインターフェースを抽出し、そのインターフェースを使用してドメインロジッククラスから接続し、テスト中のインターフェース。
weltraumpirat、2013

1
私は完全に同意します。サードパーティのライブラリを含め、ここにコードの塊をアップロードせずに全体像を説明することは困難です。
Victor Grazi

1
たぶんできます。しかし、それは「より良い方法」ではありません。データベースコードは、アプリケーションの残りの部分から隠したい、おそらく別のパッケージに移動したい実装の詳細です。sequelステートメントを変更するたびにドメインロジックを再コンパイルする必要はありませんか?
weltraumpirat

回答:


643

質問に直接回答するには、はい、他のモックを作成せずに一部のメソッドをモックすることができます。これは部分モックと呼ばれます。詳細については、部分的なモックに関するMockitoのドキュメントを参照しください。

あなたの例では、テストで次のようなことをすることができます:

Stock stock = mock(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
when(stock.getValue()).thenCallRealMethod();  // Real implementation

その場合、句で指定さthenCallRealMethod()れていない限り、各メソッドの実装はモックされますwhen(..)

モックの代わりにスパイを使って逆の可能性もあります:

Stock stock = spy(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
// All other method call will use the real implementations

その場合、すべてのメソッドの実装は実際のものです。ただし、でモック動作を定義した場合は除きますwhen(..)

when(Object)前の例のようにスパイで使用する場合、1つの重要な落とし穴があります。実際のメソッドが呼び出されます(実行時にstock.getPrice()評価されるためwhen(..))。メソッドに呼び出すべきでないロジックが含まれている場合、これは問題になる可能性があります。前の例は次のように書くことができます:

Stock stock = spy(Stock.class);
doReturn(100.00).when(stock).getPrice();    // Mock implementation
doReturn(200).when(stock).getQuantity();    // Mock implementation
// All other method call will use the real implementations

別の可能性org.mockito.Mockito.CALLS_REAL_METHODSとしては、次のものを使用することが考えられます。

Stock MOCK_STOCK = Mockito.mock( Stock.class, CALLS_REAL_METHODS );

これにより、スタブ化されていない呼び出しが実際の実装に委任されます。


しかし、あなたの例で、私はそれがまだの実装があるため、失敗すると信じgetValue()に依存しているquantityprice、いうよりgetQuantity()getPrice()あなたが嘲笑たものです。

別の可能性は、モックを完全に回避することです:

@Test
public void getValueTest() {
    Stock stock = new Stock(100.00, 200);
    double value = stock.getValue();
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}

21
この答えは間違っていると思います。クラスをMOCKするのではなく、オブジェクトのインスタンスをSPYする必要があります。
GaRRaPeTa 2014

2
@GaRRaPeTaスパイとモックはどちらも合理的な選択肢だと思います。OPはこれが単純化された例であると述べているため、どちらがこのケースに最適であるかを言うのは困難です。
Jon Newmuis 14

1
「モック」ではなく「スパイ」である必要はありません。部分的なモッキングケーブは「スパイ」によってより良い方法で提供されるからです。
Tarun Sapra 2016年

2
Stock stock = spy(Stock.class);これは間違っているようです、spyメソッドはクラスではなくオブジェクトのみを受け入れるようです。
Paramvir Singh Karwal

4
doReturn(retval).when(spyObj).methodName(args)との違いを指摘するための+1when(spyObj.methodName(args)).thenReturn(retval)
Captain_Obvious

140

クラスの部分的なモッキングは、mockitoのSpyでもサポートされています

List list = new LinkedList();
List spy = spy(list);

//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);

//using the spy calls real methods
spy.add("one");
spy.add("two");

//size() method was stubbed - 100 is printed
System.out.println(spy.size());

1.10.192.7.22ドキュメントで詳細な説明を確認してください。


37

ドキュメントによると:

Foo mock = mock(Foo.class, CALLS_REAL_METHODS);

// this calls the real implementation of Foo.getSomething()
value = mock.getSomething();

when(mock.getSomething()).thenReturn(fakeValue);

// now fakeValue is returned
value = mock.getSomething();

2
テストから制御する必要があるいくつかのメソッドを除いて、すべてのメソッドに対して実際の実装が呼び出されるモックをセットアップする方法を説明していただきありがとうございます。
bigh_29

class NaughtyLinkedList extends LinkedList { public int size() { throw new RuntimeException("don't call me");} } @Test public void partialMockNaughtLinkedList(){ List mock = mock(NaughtyLinkedList.class, CALLS_REAL_METHODS); mock.add(new Object()); // this calls the real function when(mock.size()).thenReturn(2); // For whatever reason, this lines throws the RuntimeException. assertEquals(2,mock.size()); }これは機能しません。理由が何であれ、「いつ」が実行されると、モックされるはずのメソッドが実際に実行されます。コード:
ランスの種類

3
問題は「いつ」ですか。「いつ」は、部分的にモックしたいものを実際に実行します。これを回避するには、代替手段としてdoReturn()があります。doReturn()で参照docs.mockito.googlecode.com/hg/1.9.5/org/mockito/...
ランス・カインド

18

あなたが欲しいのはorg.mockito.Mockito.CALLS_REAL_METHODSドキュメントによると:

/**
 * Optional <code>Answer</code> to be used with {@link Mockito#mock(Class, Answer)}
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations.
 * <p>
 * This implementation can be helpful when working with legacy code.
 * When this implementation is used, unstubbed methods will delegate to the real implementation.
 * This is a way to create a partial mock object that calls real methods by default.
 * <p>
 * As usual you are going to read <b>the partial mock warning</b>:
 * Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects.
 * How does partial mock fit into this paradigm? Well, it just doesn't... 
 * Partial mock usually means that the complexity has been moved to a different method on the same object.
 * In most cases, this is not the way you want to design your application.
 * <p>
 * However, there are rare cases when partial mocks come handy: 
 * dealing with code you cannot change easily (3rd party interfaces, interim refactoring of legacy code etc.)
 * However, I wouldn't use partial mocks for new, test-driven & well-designed code.
 * <p>
 * Example:
 * <pre class="code"><code class="java">
 * Foo mock = mock(Foo.class, CALLS_REAL_METHODS);
 *
 * // this calls the real implementation of Foo.getSomething()
 * value = mock.getSomething();
 *
 * when(mock.getSomething()).thenReturn(fakeValue);
 *
 * // now fakeValue is returned
 * value = mock.getSomething();
 * </code></pre>
 */

したがって、コードは次のようになります。

import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

public class StockTest {

    public class Stock {
        private final double price;
        private final int quantity;

        Stock(double price, int quantity) {
            this.price = price;
            this.quantity = quantity;
        }

        public double getPrice() {
            return price;
        }

        public int getQuantity() {
            return quantity;
        }

        public double getValue() {
            return getPrice() * getQuantity();
        }
    }

    @Test
    public void getValueTest() {
        Stock stock = mock(Stock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
        when(stock.getPrice()).thenReturn(100.00);
        when(stock.getQuantity()).thenReturn(200);
        double value = stock.getValue();

        assertEquals("Stock value not correct", 100.00 * 200, value, .00001);
    }
}

コールStock stock = mock(Stock.class);の呼び出しorg.mockito.Mockito.mock(Class<T>)のようになります。

 public static <T> T mock(Class<T> classToMock) {
    return mock(classToMock, withSettings().defaultAnswer(RETURNS_DEFAULTS));
}

値のドキュメントは次のRETURNS_DEFAULTSように伝えます:

/**
 * The default <code>Answer</code> of every mock <b>if</b> the mock was not stubbed.
 * Typically it just returns some empty value. 
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations. 
 * <p>
 * This implementation first tries the global configuration. 
 * If there is no global configuration then it uses {@link ReturnsEmptyValues} (returns zeros, empty collections, nulls, etc.)
 */

1
よく見つかる...しかし、なぜあなたwithSettings()...がそのように使うのかを尋ねてもいいですか それを表示されるorg.mockito.internal.stubbing.answers.CallsRealMethods()仕事をするかもしれない(例えば)...と、このクラスのjavadocは、具体的には、部分的モックのために使用するためだと言う...
マイクげっ歯類

3
また、これはここで他の回答が遭遇する問題に遭遇しthenReturnないでしょう:すなわち、実際にメソッドを実行します(この例ではありませんが、問題を引き起こす可能性があります)doReturn
マイクげっ歯類

4

上記の回答ですでに述べたように、Mockitoのスパイメソッドを使用した部分的なモッキングが問題の解決策になる可能性があります。あなたの具体的なユースケースでは、DBルックアップをモックする方が適切かもしれないと私はある程度同意します。私の経験から、これは常に可能であるとは限りません-少なくとも他の回避策がないと-非常に扱いにくいか、少なくとも壊れやすいと考えます。部分的なモッキングは、味方バージョンのMockitoでは機能しないことに注意してください。少なくとも1.8.0を使用している。

この回答を投稿する代わりに、元の質問に簡単なコメントを書いただけですが、StackOverflowではこれを許可していません。

もう1つだけ、ここで質問が何度も行われることを理解できません。少なくとも問題を理解しようとしないと、「なぜこれを実行したいのか」というコメントが返されます。特に、部分的なモックが必要になると、それがどこに役立つか想像できるユースケースが本当にたくさんあります。そのため、Mockitoのスタッフがその機能を提供しました。もちろん、この機能は使いすぎないでください。しかし、非常に複雑な方法では確立できなかったテストケースのセットアップについて話すときは、スパイを使用する必要があります。


2
この答えは部分的には意見だと思います。編集をご検討ください。
soundlikeodd 2017年

2
家族の新しいメンバーを応援することに賛成。これを-veゾーンで取得する必要はありません。実際には技術的に問題はありませんし、言語/調子も正しくありません。新しいメンバーに親切にしてください。ありがとう。
Saurabh Patil
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.