Development Java

@MockBeanと@Mock、Mockito.mock()の違い

投稿日:2019年3月1日 更新日:

概要

@Mock、@MockBean、Mockito.mock()の違いを調べたので備忘録を兼ねてまとめておきます。

Spring Bootのアプリケーションなどをテストする時に便利なモックオブジェクトですが、他の人が書いたコードを見ていると、@Mockや@MockBean、Mockito.mock()メソッドを使ってモックをするコードをよく見かけます。

@MockとMockito.mock()はMockitoなのですが、@MockBeanはSpring Frameworkのアノテーションとなっています。Spring Bootは自動的にMockitoを使えるようにしてくれるので便利ですが、違いをわからないと困ることもあります。

Mockito.mock()

Mockito.mock() は手動でモックオブジェクトを生成する方法です。

Mockitoのメソッドをじかに呼び出してモックを作るこの方法は、普通のテストクラスで使えます。

@Mock

@Mock アノテーションを使うと、 Mockito.mock()メソッドを使わずにモックオブジェクトを作れます。

上の例のように、クラス変数の上に @Mock をつけることで、mockRepositoryにはモックオブジェクトがセットされます。

このアノテーションを処理させるには、 @RunWith MockitoJUnitRunner.classを指定するか、 MockitoAnnotations.initMocks() を手動で呼び出す必要があります。

@MockBean

@MockBean アノテーションはMockitoではなくSpring Bootが提供するアノテーションです。 @MockBeanを使うと、生成されたモックオブジェクトはアプリケーションコンテキスト内の同じ型のビーンを書き換えます。

上の例は UserServiceUserRepositoryに依存しているケースですが、 userRepositoryには @MockBeanアノテーションがついているため、モックオブジェクトが生成され、同時にアプリケーションコンテキストに追加されます。

そのため、 userServiceにはモックされた UserRepositoryがインジェクトされた状態で返されます。

上の@Mockアノテーションと似ていますが、Springのアプリケーションコンテキストにモックオブジェクトを登録するかどうかが変わってきます。また、このアノテーションを処理させるには、1行目で行なっているように、 @RunWith SpringRunner.classを指定する必要があります。

MockitoかSpring Bootかの違い

以上をフレームワーク別に分けると

@MockMockito.mock()はMockito

@MockBeanは Spring Boot

ということになるので、どちらのツールを使うべきかわかりやすくなります。

 

一般的にSpring Bootを使ったテストは非常に遅いので、ユニットテストには適しません

Spring Bootを使ったテストは、Springのアプリケーションを完全にロードしてからテストを実行します。そのため、アプリケーションが大きくなれななるほど動作が遅くなり、ちょっとユニットテストをするだけでも起動に10秒以上かかってしまいます。

理想的にはユニットテストは一瞬で終わるように書くべきだといわれますし、実際に僕もそう思います。

テストが起動するまでに毎回10秒とかかかってしまうと、時間をムダにしている感じがありますし、それが原因でテストしたくないとなってしまうのは本末転倒です。

そのためには、自分がテストしているロジック以外は全てモックしてしまうMockitoを使ったテストが理にかなっているといえます。

ユニットテスト= すべてMockitoで対応

どうしてもモックでテストを書けないときや結合テストだけ Spring Boot Test

と分けるとスッキリするかもしれません。

@MockBeanを使うとアプリケーションコンテキストが再生成される

@MockMockito.mock()@MockBeanの違いをまとめましたが、これを調べたきっかけは、 @MockBeanを使ってテストを書いていた時におかしな問題が発生したためです。

その問題とは、

  1. @MockBeanを使って外部サービスをモックしたテストクラスを作成した
  2. IDEからこのテストクラスだけを実行すると正常に動作する
  3. 他のテストクラスとまとめて実行すると、 @MockBeanを使っているクラスに差し掛かったところでFailed to create application contextというエラーが発生する
  4. ログを見ると、テスト開始前にすでに生成されたはずのデータベーステーブルを作成しようとしてHibernateがエラーを出していた

というものでした。

使用していたのはSpring Bootの1.5.15.RELEASEと1.5.19.RELEASEですが、これはSpringのバグではないのでどのバージョンでも同じ問題が起こるはずです。Spring Frameworkは「本当に同じ人間が作っているのか」と思うくらいに安定感と品質がズバ抜けて高いので、何かおかしいときはまずは自分の問題を疑うのが無難です…

動作を見ていると、Spring Boot Testは @MockBeanを付与したテストクラスを見つけると、そこでアプリケーションコンテキストを再生成するようです。

僕のアプリケーションは少し特殊な構成になっていて、スキーマの生成をJavaコードで行なっていました。そのため、以下のようなことが起こっていました。

  1. テスト実行前にSpring Bootがアプリケーションコンテキストを作成。同時にスキーマも作成される
  2. テストが普通に実行される
  3. @MockBeanが付与されたクラスを実行する前に、Spring Bootがアプリケーションコンテキストを再作成。同時にスキーマも再作成。
  4.  しかしDB(H2)は再起動されず、すでにテーブルも存在するので[Table already exists]エラーが発生する。

回避策としては、

  1. スキーマ生成のコードで例外を無視させる
  2. アプリケーションコンテキストが終了する前にスキーマを削除する
  3. @MockBeanを使わない

という選択肢がありました。

僕はとりあえず3番目で対処しました。方法としては@Mockアノテーションを使い、モックが必要なところで適宜モックオブジェクトをテスト対象のクラスにセットするという方法です。

ただ他の人が知らずに @MockBeanを使ったら問題が再発するので、一番すっきりするのは2番目かもしれません。

その場合、スキーマを生成したBeanに @PreDestroyをつけたメソッドを作成して、そこでスキーマの削除を行えばエラーは発生しなくなりました。便利な機能ですが、あちこちのクラスで @MockBeanを使うと、その度にアプリケーションコンテキストの再生成が行われるので、テストの実行がかなりもっさりします。

テストが存在しないよりははるかにマシですが、Spring Bootを使ったテストはただでさえオーバーヘッドが大きいので使いどころを限定しないとストレスがたまりますね。

-Development, Java

Copyright© 技術系のメモ , 2019 All Rights Reserved.