web-dev-qa-db-ja.com

他のメソッドではなく一部のメソッドをモックするためにMockitoを使う

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);
}
307
Victor Grazi

直接あなたの質問に答えるために、はい、あなたは他のものをあざけることなくいくつかの方法をあざけることができます。これは 部分モック と呼ばれます。 部分モックに関する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(..)を指定しない限り、各メソッド実装はモックされます。

mock の代わりに spy で別の方法で囲むことも可能です。

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)をspyと一緒に使用する際の重要な落とし穴が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

しかし、あなたの例では、getValue()の実装はgetQuantity()getPrice()ではなくquantitypriceに依存しているので、まだ失敗すると思います。

本当にあなたが望むように見えるのは、ただ:

@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);
}
483
Jon Newmuis

クラスの部分的なモックは、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.19 および 2.7.22 docsを確認してください。

124
Sudarshan

docs :によると

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();
25
ema

受け入れられた答えは質問によると正しくありません。

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.)
 */

あなたが欲しいものはdocsによると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);
    }
}
16
the dude

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

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

もう1つ重要なことがあります。ここで質問されることが何度もあるのですが、問題を理解しようとせずに「なぜこれをやりたいのか」というコメントを得ることができません。特に部分的なモックが必要になるとなると、特に便利なことが想像できるユースケースが本当にたくさんあります。 Mockitoの人たちがその機能を提供したのはそのためです。もちろんこの機能は使い過ぎないようにしてください。しかし、それ以外では非常に複雑な方法では確立できなかったテストケースの設定について説明するときは、スパイを使用する必要があります。

0
kflGalore