web-dev-qa-db-ja.com

Mockito、JUnit、およびSpring

私は今日だけモッキートについて学び始めました。いくつかの簡単なテスト(JUnitを使用、以下を参照)を作成しましたが、SpringのマネージドBean内でモックオブジェクトをどのように使用できるかわかりません。 Springと連携するためのベストプラクティスとは何ですか。 Beanにモックされた依存関係をどのように注入すればよいですか?

これをスキップして、私の質問に戻る

まず、私が学んだこと。これは非常に良い記事です モックはスタブではありません 基本を説明します(モックのチェック動作検証ではなく状態検証)。そして、ここに良い例があります Mockito そしてここに mockitoで簡単にモックする 。 Mockitoのモックオブジェクトはmockstubの両方であるという説明があります。

ここ Mockito およびここ Matchers では、さらに例を見つけることができます。

このテスト

@Test
public void testReal(){
    List<String> mockedList = mock(List.class);
     //stubbing
     //when(mockedList.get(0)).thenReturn("first");

    mockedList.get(anyInt());
    OngoingStubbing<String> stub= when(null);
    stub.thenReturn("first");

    //String res = mockedList.get(0);
                //System.out.println(res);

     //you can also verify using argument matcher
     //verify(mockedList).get(anyInt());

    verify(mockedList);
    mockedList.get(anyInt());
}

うまく動作します。

私の質問に戻ります。ここで Spring BeanにMockitoモックを挿入 誰かがSprings ReflectionTestUtils.setField()を使用しようとしますが、それからここに Spring Integration Tests、Creating Mock Objects Springのコンテキストをchangeにすることをお勧めします。

最後の2つのリンクを本当に理解していませんでした... MockitoでSpringにどんな問題があるのか​​、誰かに説明してもらえますか?このソリューションの何が問題になっていますか?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}

https://stackoverflow.com/a/8742745/1137529

編集:私はあまり明確ではありませんでした。自己を明確にするコードの3つの例を提供します。たとえば、メソッドprintHello()を持つBean HelloWorldと、HelloWorldのメソッドprintHello()への呼び出しを転送するメソッドsayHelloを持つBean HelloFacadeがあるとします。

最初の例は、Springのコンテキストを使用し、カスタムランナーなしで、依存性注入(DI)にReflectionTestUtilsを使用しています。

public class Hello1Test  {
private ApplicationContext ctx;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml");
}



@Test
public void testHelloFacade() {
    HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class);
    HelloWorld mock = mock(HelloWorld.class);
    doNothing().when(mock).printHello();

    ReflectionTestUtils.setField(obj, "hello", mock);
    obj.sayHello();

    verify(mock, times(1)).printHello();
}

}

@Noamが指摘したように、MockitoAnnotations.initMocks(this);への明示的な呼び出しなしで実行する方法があります。この例では、Springのコンテキストの使用も省略します。

@RunWith(MockitoJUnitRunner.class)
public class Hello1aTest {


@InjectMocks
private HelloFacade obj =  new HelloFacadeImpl();

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

これを行う別の方法

public class Hello1aTest {

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}


@InjectMocks
private HelloFacadeImpl obj;

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

また、前の例では、HelloFacadeはインターフェイスであるため、HelloFacadeImplを手動でインスタンス化し、HelloFacadeに割り当てる必要があります。最後の例では、HelloFacadeImplを宣言するだけで、Mokitoがインスタンス化してくれます。現在テスト中のユニットであるこのアプローチの欠点は、インターフェースではなく実装クラスです。

75
alexsmail

正直に言って、あなたの質問を本当に理解しているかどうかはわかりません:Pあなたの元の質問から得たものから、できる限り明確にしようとします:

まず、ほとんどの場合、Springについて心配する必要はありません。ユニットテストの作成に春を巻き込む必要はほとんどありません。通常の場合、単体テストでテスト対象システム(SUT、テスト対象)をインスタンス化し、テストでもSUTの依存関係を挿入するだけです。依存関係は通常、モック/スタブです。

元の提案された方法、および例2、3は、上記で説明したことを正確に実行しています。

まれなケース(統合テスト、特別な単体テストなど)では、Springアプリコンテキストを作成し、アプリコンテキストからSUTを取得する必要があります。そのような場合、私はあなたができると信じています:

1)SpringアプリctxでSUTを作成し、それへの参照を取得し、それにモックを注入します

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Before
    /* Initialized mocks */
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void someTest() {
         // ....
    }
}

または

2)リンクで説明されている方法に従います Spring Integration Tests、Creating Mock Objects 。このアプローチは、Springのアプリコンテキストでモックを作成することです。アプリのctxからモックオブジェクトを取得して、スタブ/検証を行うことができます。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

両方の方法が機能するはずです。主な違いは、前者の場合は春のライフサイクルなどを経て依存関係が注入されることです(たとえば、Beanの初期化)。後者の場合は事前に注入されます。たとえば、SUTがスプリングのInitializingBeanを実装し、初期化ルーチンに依存関係が含まれる場合、これら2つのアプローチの違いがわかります。あなたが何をしているのかを知っている限り、これらの2つのアプローチには正しいことも間違っていることもないと思います。

Mockitoを使用する場合、@ Mock、@ Inject、MocktoJunitRunnerなどだけがすべて不要です。これらは、Mockito.mock(Foo.class)と多数のセッター呼び出しを入力する手間を省くための単なるユーティリティです。

53
Adrian Shum

あなたの質問は、あなたが与えた3つの例のどれが好ましいアプローチであるかについて尋ねているようです。

例1 Reflection TestUtilsを使用することは、単体テストには適していません。あなたは本当に単体テストのために全くスプリングコンテキストをロードしたくありません。他の例で示されているように、必要なものを模擬して注入するだけです。

いくつかの統合テストを実行する場合は、スプリングコンテキストをロードする必要がありますが、@RunWith(SpringJUnit4ClassRunner.class)を使用してBeanに明示的にアクセスする必要がある場合は、@Autowiredとともにコンテキスト。

例2は有効なアプローチであり、@RunWith(MockitoJUnitRunner.class)を使用すると、@ BeforeメソッドとMockitoAnnotations.initMocks(this);の明示的な呼び出しを指定する必要がなくなります。

は、@RunWith(...)を使用しない別の有効なアプローチです。テスト対象のクラスを明示的にインスタンス化していないHelloFacadeImplですが、例2でも同じことができます。

私の提案は、コードの混乱を減らすため、ユニットテストに例2を使用することです。強制された場合は、より詳細な構成にフォールバックできます。

6
Brad

Spring 4.2.RC1のいくつかの新しいテスト機能の導入により、SpringJUnit4ClassRunnerに依存しないSpring統合テストを作成できます。 this ドキュメントの一部をご覧ください。

あなたの場合、Spring統合テストを書いても、このようなモックを使用できます:

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}
4
geoand

これが私の短い要約です。

単体テストを作成する場合は、単体テストを行うクラスに実際の依存関係を注入したくないため、Spring applicationContextを使用しないでください。代わりに、クラスの上の@RunWith(MockitoJUnitRunner.class)アノテーション、または@BeforeメソッドのMockitoAnnotations.initMocks(this)でモックを使用してください。

統合テストを作成する場合は、次を使用します。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")

たとえば、メモリ内データベースを使用してアプリケーションコンテキストを設定するには。通常、統合テストではモックを使用しませんが、上記のMockitoAnnotations.initMocks(this)アプローチを使用してモックを使用できます。

2
Adriaan Koster

Mockito 1.9(またはそれ以降)を使用している場合、MockitoAnnotations.initMocks(this);は本当に必要ありません-必要なのはこれだけです:

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

@InjectMocksアノテーションは、すべてのモックをMyTestObjectオブジェクトに注入します。

2
Noam

プロジェクトをSpring Boot 1.4に移行する場合、偽のMyDependentObjectに新しいアノテーション @MockBean を使用できます。この機能を使用すると、Mockitoの@Mockおよび@InjectMocks注釈をテストから削除できます。

0
luboskrnac

@InjectMocksアノテーション付きフィールドをインスタンス化する必要があるかどうかの違いは、MockitoJunitRunnerまたはMockitoAnnotations.initMocksを使用するかどうかではなく、Mockitoのバージョンにあります。 1.9では、@Mockフィールドのコンストラクタインジェクションも処理し、インスタンス化を行います。以前のバージョンでは、自分でインスタンス化する必要があります。

これが、Spring Beanの単体テストの方法です。問題はない。 Spring構成ファイルを使用してモックのインジェクションを実際に行いたい場合、人々は混乱に陥ります。これは、単体テストと統合テストのポイントを超えています。

もちろんテスト対象のユニットはImplです。本当の具体的なことをテストする必要がありますよね?インターフェイスとして宣言した場合でも、テストするために本物をインスタンス化する必要があります。これで、実際のオブジェクトのスタブ/モックラッパーであるスパイにアクセスできますが、これはコーナーケース用です。

0
jhericks