Spock + Mockitoでユニットテストを書いているときに、finalなクラスをモック化したくなったので、PowerMockを導入。
JUnit 4で使う時と同様、 @RunWith(PowerMockRunner)
しただけでは実行時にエラーがでてしまったので、SpockでPowerMockを使う方法をメモ。
環境
Spock 1.3-groovy-2.5、Mockito 3.3.3、PowerMock 2.0.9。
PowerMockは powermock-module-junit4
と powermock-api-mockito2
を利用。
問題
finalなクラスをモック化したいと思い、まずは test/resources/mockito-extensions/org.mockito.plugins.MockMaker
を作成して mock-maker-inline
を追加したところ、ローカルでのテストでは成功したが、CIで失敗。
原因調査の時間がなかったため、PowerMockを導入した。
テスト実行にはSpockを利用しているため、 powermock-module-junit4
と powermock-api-mockito2
を依存関係に追加し、JUnit 4で使う時と同様、 @RunWith(PowerMockRunner)
および @PrepareForTest(テスト対象クラス)
をテストクラスに付与したが、テストを実行すると以下のエラーが発生する。
Execution failed for task ':test'. > No tests found for given includes: [テストクラス](filter.includeTestsMatching)
どうやら、テストメソッドがないと判断されている模様。
調査
Stack Overflowに質問があった。
対応
PowerMockRule
の利用がベストアンサーとなっているが、 powermock-module-junit4-rule
を依存関係に追加し、テストクラスの @RunWith(PowerMockRunner)
を削除して @Rule PowerMockRule powerMockRule = new PowerMockRule()
を追加してみても、やはりエラーが出てしまう。
ベストアンサーではないが、同じ質問の回答に @PowerMockRunnerDelegate(Sputnik.class)
を使うというものがあった。
こちらを試してみたところうまくいった。
import org.junit.runner.RunWith import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import org.powermock.modules.junit4.PowerMockRunnerDelegate import org.spockframework.runtime.Sputnik @RunWith(PowerMockRunner) @PowerMockRunnerDelegate(Sputnik) @PrepareForTest(TargetClass) class TargetClassSpec extends Specification { def "テストメソッド"() { given: def target = PowerMockito.mock(TargetClass) PowerMockito.doReturn(true).when(target)... } }
PowerMockを使うと、デフォルトのテストランナーはJUnitになる模様。Spock v1系のテストランナーである Sputnik
を明示的に指定することで、SpockでもPowerMockを使用できた。
振り返り
PowerMockRule
ではなく @RunWith(PowerMockRunner)
を利用しているのは、以前は他のRuleと併用できなかったのと、 powermock-module-junit4-rule
を依存性に追加しないと PowerMockRule
が使えないから。
ずっと @RunWith(PowerMockRunner)
を使っているが、JUnitであれば他のRuleと PowerMockRule
、併用できるようになっているのだろうか。
また、5月にリリースされたSpock v2では、Sputnikが削除されている模様。記事内で思いっきり PowerMockRunnerDelegate
が例として記述されている。この方法、Spock使いにはおなじみの方法だったんだろうか。
代替手段としては、 @PowerMockRunnerDelegate(JUnitPlatform)
に変更し、JUnit4を使ってテストしてくれとのこと。
余談
もともとMockitoを使ったテストが書いてあったクラスに、今回の設定を混ぜ込んだところ、以下の例外が発生した。
Notifications are not supported when all test-instances are created first! Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST Method was too large and after instrumentation exceeded JVM limit. PowerMock modified the method to allow JVM to load the class. You can use PowerMock API to suppress or mock this method behaviour. java.lang.IllegalAccessException: Method was too large and after instrumentation exceeded JVM limit. PowerMock modified the method to allow JVM to load the class. You can use PowerMock API to suppress or mock this method behaviour.
PowerMockがクラスのバイトコードを書き換えた結果、メソッドのサイズがJVMの上限を超えたらしい。テストクラスを分割して対応した。