Spock v1.3でPowerMockを使う

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-junit4powermock-api-mockito2 を利用。

問題

finalなクラスをモック化したいと思い、まずは test/resources/mockito-extensions/org.mockito.plugins.MockMaker を作成して mock-maker-inline を追加したところ、ローカルでのテストでは成功したが、CIで失敗。

原因調査の時間がなかったため、PowerMockを導入した。

テスト実行にはSpockを利用しているため、 powermock-module-junit4powermock-api-mockito2 を依存関係に追加し、JUnit 4で使う時と同様、 @RunWith(PowerMockRunner) および @PrepareForTest(テスト対象クラス) をテストクラスに付与したが、テストを実行すると以下のエラーが発生する。

Execution failed for task ':test'.
> No tests found for given includes: [テストクラス](filter.includeTestsMatching)

どうやら、テストメソッドがないと判断されている模様。

調査

Stack Overflowに質問があった。

stackoverflow.com

対応

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 が使えないから。

irof.hateblo.jp

ずっと @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の上限を超えたらしい。テストクラスを分割して対応した。

stackoverflow.com