こういうクラスがあったとする。
public class Client { public void connect() { throw new RuntimeException(); } }
public class Service { public int test() { System.out.println("test"); new Client().connect(); return 1; } }
Clientはネットワーク通信を行うクラスで、Serviceはそれを呼び出して実行するクラス(面倒くさかったので中身は仮)。これでService.testのテストコードを書こうとするとテストのたびにClient.connectメソッドが呼び出されてネットワーク接続が発生してしまうので、何とかしてClientクラスをモック化したい。
以下のようなテストコードを書いてみたりもしたが、うまく動かなかった。SpyをGroovyMockとかにしても同様に駄目だった。
class ServiceSpec extends Specification { def 'test' () { given: def mockClient = Spy(Client) mockClient.connect() >> void when: def actual = new Service().test() then: actual == 1 } }
こういうことをやる場合はtestメソッド内でインスタンス化するのではなくインスタンスをServiceコンストラクタに引き渡すようにして実装するか、SpringとかであればDIでClientインスタンスをService側に引き渡すようにして実装すべきだったんだと思う。実際以下のようなコードであればうまく動作した。
public class Service { private final Client client; public Service(Client client) { this.client = client; } public int test() { System.out.println("test"); client.connect(); return 1; } }
class ServiceSpec extends Specification { def 'test' () { given: def mockClient = Mock(Client) mockClient.connect() >> {} when: def actual = new Service(mockClient).test() then: actual == 1 } }
後になってPowerMockを使ってやればできそうだということに気が付いたけど、問題のコードのあるプロジェクトはSpring Frameworkを利用しており、かつSpring+Spock+PowerMockの組み合わせだとちょっとうまく動かなかった。以下でも同じようなことをやろうとしているが、やはりうまく動いていない(エラー内容はこちらとちょっと違う感じだが)。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( 番外編 )( static メソッドをモック化してテストするには? ) - かんがるーさんの日記
Spock自体は悪くない……というより慣れれば便利だと感じるようにはなってきたが、他のテスト用ライブラリと組み合わせて使うのはちょっと難易度高いのかもしれない。単純に自分の知識不足もあるけど。