Mockito:メソッド内で作成されたオブジェクトでメソッドが呼び出されたことを確認する方法は?


322

モッキートは初めてです。

以下のクラスが与えられた場合、どのようにしてMockitoを使用して、someMethod呼び出された直後fooに呼び出されたかを確認できますか?

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}

次の確認電話をかけたいのですが、

verify(bar, times(1)).someMethod();

どこbarの嘲笑インスタンスですBar


2
stackoverflow.com/questions/6520242/…-しかし、PowerMockを使いたくありません。
mre

APIまたはPowerMockを変更します。2つのうちの1つ。
ジョンB

このようなものをカバーする方法?? public synchronized void start(BundleContext bundleContext)が例外をスローします{BundleContext bc = bundleContext; logger.info( "STARTING HTTP SERVICE BUNDLE"); this.tracker = new ServiceTracker(bc、HttpService.class.getName()、null){@Override public Object addingService(ServiceReference serviceRef){httpService =(HttpService)super.addingService(serviceRef); registerServlets(); httpServiceを返します。}}}
ShAkKiR 2018

回答:


366

依存性注入

Barインスタンス、またはBarインスタンスの作成に使用されるファクトリー(またはこれを行う他の483通りの方法の1つ)をインジェクトすると、テストを実行するために必要なアクセス権が得られます。

工場の例:

次のように記述されたFooクラスがあるとします。

public class Foo {
  private BarFactory barFactory;

  public Foo(BarFactory factory) {
    this.barFactory = factory;
  }

  public void foo() {
    Bar bar = this.barFactory.createBar();
    bar.someMethod();
  }
}

テストメソッドでは、次のようにBarFactoryを注入できます。

@Test
public void testDoFoo() {
  Bar bar = mock(Bar.class);
  BarFactory myFactory = new BarFactory() {
    public Bar createBar() { return bar;}
  };

  Foo foo = new Foo(myFactory);
  foo.foo();

  verify(bar, times(1)).someMethod();
}

おまけ:これは、TDDがコードの設計を推進する方法の例です。


6
単体テスト用にクラスを変更せずにこれを行う方法はありますか?
mre

6
Bar bar = mock(Bar.class)代わりにBar bar = new Bar();
ジョンB

7
私が知っていることではありません。ただし、単体テストのためだけにクラスを変更することはお勧めしません。これは、クリーンなコードとSRPについての会話です。または、Barオブジェクトを作成するのはクラスFooのメソッドfoo()の責任です。答えが「はい」の場合、それは実装の詳細であり、相互作用を具体的にテストすることについて心配する必要はありません(@Michaelの答えを参照)。答えが「いいえ」の場合は、クラスを変更していることになります。テストの難しさは、設計に少し改善が必要であることの赤信号です(したがって、TDDが設計を駆動する方法について追加したボーナス)。
csturtz

3
「実際の」オブジェクトをMockitoの「検証」に渡すことはできますか?
ジョンB

4
また、工場を模擬することができます BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa

18

古典的な応答は、「あなたはしません」です。Foo内部ではなく、のパブリックAPIをテストします。

Foo影響を受けるオブジェクト(または環境内の他のオブジェクト)の動作はありますfoo()か?もしそうなら、それをテストします。そうでない場合、メソッドは何をしますか?


4
ここで実際に何をテストしますか?公開API FooIS public void foo()内部にある、唯一の関連するバー。
2016

15
テストを必要とする副作用のある本物のバグが見つかるまで、パブリックAPIのみをテストしても問題ありません。たとえば、プライベートメソッドがHTTP接続を適切に閉じていることを確認することは、プライベートメソッドが接続を適切に閉じていないことを発見するまで行き過ぎであり、そのために大きな問題を引き起こしています。その時点で、verify()統合テストの聖なる祭壇で崇拝しなくなったとしても、Mockitoは確かに非常に役立ちます。
ドーンガーポニー2016

@DuffJ私はJavaを使用していませんが、コンパイラやコード分析ツールで検出する必要があるように思えます。
user247702 16

3
私はDuffJに同意します。関数型プログラミングは楽しいですが、コードが外の世界と相互作用するようになります。それを「内部」、「副作用」、「機能」と呼ぶかどうかは関係ありません。その相互作用をテストする必要があります。それが発生するかどうか、それが正しい回数と正しい引数で発生するかどうかです。@Stijn:悪い例かもしれません(しかし、複数の接続を開いて、そのうちのいくつかだけを閉じると、面白くなるかもしれません)。より良い例は、正しいデータが接続を介して送信されているはずの天気を確認することです。
AndrasBalázsLajtha 2017

13

DIまたはファクトリーを使用したくない場合。少しトリッキーな方法でクラスをリファクタリングできます:

public class Foo {
    private Bar bar;

    public void foo(Bar bar){
        this.bar = (bar != null) ? bar : new Bar();
        bar.someMethod();
        this.bar = null;  // for simulating local scope
    }
}

そしてあなたのテストクラス:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock Bar barMock;
    Foo foo;

    @Test
    public void testFoo() {
       foo = new Foo();
       foo.foo(barMock);
       verify(barMock, times(1)).someMethod();
    }
}

次に、fooメソッドを呼び出すクラスは次のようにします。

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}

この方法でメソッドを呼び出すときにわかるように、fooメソッドを呼び出している他のクラスにBarクラスをインポートする必要はありません。

もちろん、欠点は、呼び出し側がBarオブジェクトを設定できるようにすることです。

それが役に立てば幸い。


3
これはアンチパターンだと思います。依存関係を注入する必要があります、期間。テスト目的でのみ、オプションで挿入された依存関係を許可することは、意図的にコードの改善を回避することであり、本番環境で実行されるコードとは異なる何かを意図的にテストします。どちらもひどくて恐ろしいことです。
ErikE 2018年

8

使用するサンプルコードのソリューション PowerMockito.whenNew

  • mockito-all 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • junit 4.12

FooTest.java

package foo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

//Both @PrepareForTest and @RunWith are needed for `whenNew` to work 
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {

    // Class Under Test
    Foo cut;

    @Mock
    Bar barMock;

    @Before
    public void setUp() throws Exception {
        cut = new Foo();

    }

    @After
    public void tearDown() {
        cut = null;

    }

    @Test
    public void testFoo() throws Exception {

        // Setup
        PowerMockito.whenNew(Bar.class).withNoArguments()
                .thenReturn(this.barMock);

        // Test
        cut.foo();

        // Validations
        Mockito.verify(this.barMock, Mockito.times(1)).someMethod();

    }

}

JUnit出力 JUnit出力


8

モッキート@InjectMocksは行くべき道だと思います。

あなたの意図に応じて、あなたは使うことができます:

  1. コンストラクター注入
  2. プロパティセッターインジェクション
  3. フィールド注入

ドキュメントの詳細

以下は、フィールドインジェクションの例です。

クラス:

public class Foo
{
    private Bar bar = new Bar();

    public void foo() 
    {
        bar.someMethod();
    }
}

public class Bar
{
    public void someMethod()
    {
         //something
    }
}

テスト:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
    @Mock
    Bar bar;

    @InjectMocks
    Foo foo;

    @Test
    public void FooTest()
    {
        doNothing().when( bar ).someMethod();
        foo.foo();
        verify(bar, times(1)).someMethod();
    }
}

3

はい、本当にしたい場合は、PowerMockを使用できます。これは最後の手段と考える必要があります。PowerMockを使用すると、コンストラクターの呼び出しからモックを返すことができます。次に、モックで検証を行います。そうは言っても、csturtzは「正しい」答えです。

新しいオブジェクトのモック構築へのリンクは次のとおりです


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.