web-dev-qa-db-ja.com

Mockitoを使用する場合のスプリングテストでモックをクリーンアップする方法

私はMockitoにはかなり慣れていないので、クリーンアップに問題があります。

ユニットテストにJMock2を使用していました。私の知る限り、JMock2はすべてのテストメソッドで再構築されるコンテキストで期待値とその他のモック情報を保持します。したがって、すべてのテスト方法が他の方法によって妨害されることはありません。

JMock2を使用するときに、春のテストに同じ戦略を採用しました。 post で使用した戦略に潜在的な問題が見つかりました。

私は多くの記事がMockitoを春のテストで使用することを推奨していることに気づき、試してみたいと思います。テストケースに2つのテストメソッドを記述するまで、うまく機能します。各テストメソッドは単独で実行されたときに合格し、それらの1つが一緒に実行された場合は失敗しました。これは、モック情報がモック自体に保存されているため(「JMockのようなコンテキストオブジェクトが表示されないため」)、モック(およびアプリケーションコンテキスト)が両方のテストメソッドで共有されるためだと推測しました。

@Beforeメソッドにreset()を追加して解決しました。私の質問は、この状況を処理するためのベストプラクティスは何ですか(reset()のjavadocは、reset()が必要な場合、コードは臭いだと言います)?事前に感謝します。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "file:src/main/webapp/WEB-INF/booking-servlet.xml",
    "classpath:test-booking-servlet.xml" })
@WebAppConfiguration
public class PlaceOrderControllerIntegrationTests implements IntegrationTests {

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@Autowired
private PlaceOrderService placeOrderService;

@Before
public void setup() {
    this.mockMvc = webAppContextSetup(this.wac).build();

    reset(placeOrderService);// reset mock
}

@Test
public void fowardsToFoodSelectionViewAfterPendingOrderIsPlaced()
        throws Exception {

    final Address deliveryAddress = new AddressFixture().build();
    final String deliveryTime = twoHoursLater();
    final PendingOrder pendingOrder = new PendingOrderFixture()
            .with(deliveryAddress).at(with(deliveryTime)).build();

    when(placeOrderService.placeOrder(deliveryAddress, with(deliveryTime)))
            .thenReturn(pendingOrder);

    mockMvc.perform(...);

}

@Test
public void returnsToPlaceOrderViewWhenFailsToPlaceOrder() throws Exception {

    final Address deliveryAddress = new AddressFixture().build();
    final String deliveryTime = twoHoursLater();
    final PendingOrder pendingOrder = new PendingOrderFixture()
            .with(deliveryAddress).at(with(deliveryTime)).build();

    NoAvailableRestaurantException noAvailableRestaurantException = new NoAvailableRestaurantException(
            deliveryAddress, with(deliveryTime));
    when(placeOrderService.placeOrder(deliveryAddress, with(deliveryTime)))
            .thenThrow(noAvailableRestaurantException);

            mockMvc.perform(...);

}
23
Yugang Zhou
  1. テストメソッドの後にリセットを配置することについて

    テスト中にモックをリセットする必要があるのは、テスト中に実際にクリーニングが必要なことが起こったことを示唆しているためです。

    テストメソッドの前にリセットが行われた場合、リセットする必要があるテストの前に一体何が起こったのか確信が持てません。モック以外のオブジェクトについてはどうですか?それには理由がありますか(おそらくありますか)?コードに記載されていない理由がある場合(メソッド名など)?など。

  2. Springベースのテストのファンではありません

    1. バックグラウンド

      Springの使用は、クラスの単体テストをあきらめるようなものです。 Springでは、テストをあまり制御できません:isolationinstantiationlifecycle、単体テストで見たプロパティの一部を引用します。ただし、多くの場合、Springは「transparent」ではないライブラリとフレームワークを提供します。テストのために、Spring MVC、Spring Batch、等.

      そして、多くの場合、開発者が本番コードの動作を真剣にテストするために統合テストを作成する必要があるため、これらのテストの作成は非常に面倒です。多くの開発者は、Spring内でコードがどのように存在するかについての詳細を知らないため、ユニットテストでクラスをテストしようとすると、多くの驚きにつながる可能性があります。

      しかし、問題は続きます。開発者に速いフィードバックを与えるためにテストは高速で小さくする必要があります( Infinitest などのIDEプラグインが最適です)が、テストはSpringを使用すると、本質的に遅くなり、メモリを消費します。これは、それらを実行する頻度が低く、ローカルワークステーションで完全に回避する傾向さえあります...後でCIサーバーでそれらが失敗することを発見します。

    2. MockitoとSpringのライフサイクル

      そのため、統合テストがサブシステム用に作成されると、多くのオブジェクトと明らかにコラボレーターが作成されます。ライフサイクルはスプリングランナーによって制御されますが、Mockitoモックは制御されません。そのため、モックのライフサイクルを自分で管理する必要があります。

      繰り返しますが、Spring Batchを使用したプロジェクト中のライフサイクルについては、非モックに対する残留効果に関する問題があったため、テストクラスごとに1つのテストメソッドを作成するか、 ダーティコンテキスト トリックを使用する2つの選択肢がありました:@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)。これにより、テストが遅くなり、メモリ消費量が増加しましたが、これが最良の選択肢でした。このトリックを使用すると、Mockitoモックをリセットする必要はありません。

  3. 暗闇の中で可能な光

    私はプロジェクトを十分によく知りませんが、 springockito はライフサイクルについてのいくらかの砂糖を提供できます。 annotation subproject はさらに優れているようです。Springコンテナ内のBeanのライフサイクルをSpringで管理し、テストでモックの使用方法を制御できるようです。それでもこのツールの経験はないので、驚きがあるかもしれません。

免責事項として、私はSpringが大好きで、他のフレームワークの使用を簡素化する多くの注目すべきツールを提供し、生産性を向上させ、設計を支援しますが、人間が発明したすべてのツールと同様に、常に粗いエッジがあります(それ以上... )。

余談ですが、JUnitが各テストメソッドのテストクラスをインスタンス化する際に、この問題がJUnitコンテキストで発生するのを見るのは興味深いことです。テストがTestNGに基づいている場合、TestNGはテストクラスのインスタンスを1つだけ作成するため、アプローチは少し異なる場合があります。残りのモックフィールドは必須ですSpringの使用に関係なく。


古い答え:

私は、春のconxtextでMockitoモックを使用することの大ファンではありません。しかし、あなたは次のようなものを探していますか?

@After public void reset_mocks() {
    Mockito.reset(placeOrderService);
}
38
Brice

スプリングブートには@MockBeanアノテーション これは、サービスのモックに使用できます。モックを手動でリセットする必要はもうありません。 @Autowired 沿って @MockBean

@MockBean
private PlaceOrderService placeOrderService;
8
Lu55

Springベースのテストは、高速で独立したものにするのが困難です(@Brice wrote として)。すべてのモックをリセットするための小さなユーティリティメソッドを次に示します(すべての@Beforeメソッドで手動で呼び出す必要があります)。

import org.mockito.Mockito;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;


public class MyTest {
    public void resetAll(ApplicationContext applicationContext) throws Exception {
        for (String name : applicationContext.getBeanDefinitionNames()) {
            Object bean = applicationContext.getBean(name);
            if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
                bean = ((Advised)bean).getTargetSource().getTarget();
            }
            if (Mockito.mockingDetails(bean).isMock()) {
                Mockito.reset(bean);
            }
        }
    }
}

ご覧のとおり、すべてのBeanに繰り返しがあるため、Beanがモックであるかどうかを確認し、モックをリセットします。呼び出しAopUtils.isAopProxyおよび((Advised)bean).getTargetSource().getTarget()には特に注意を払います。 Beanに@Transactional注釈が含まれている場合、このBeanのモックは常にスプリングによってプロキシオブジェクトにラップされるため、このモックをリセットまたは検証するには、最初にアンラップする必要があります。それ以外の場合は、UnfinishedVerificationExceptionを取得しますが、これは時々異なるテストで発生する可能性があります。

私の場合、AopUtils.isAopProxyで十分です。ただし、プロキシで問題が発生した場合は、AopUtils.isCglibProxyAopUtils.isJdkDynamicProxyもあります。

mockitoは1.10.19 spring-testは3.2.2.RELEASEです

4
Cherry

placeOrderServiceオブジェクトを挿入する代わりに、次のような方法で、各テストの前にMockitoで@Mockとして初期化する必要があります。

@Mock private PlaceOrderService placeOrderService;

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

Javadocの推奨事項: http://docs.mockito.googlecode.com/hg/latest/org/mockito/MockitoAnnotations.html

@Beforeメソッドをスーパークラスに入れて、@Mockオブジェクトを使用するテストケースクラスごとにメソッドを拡張することもできます。

3
superEb