概要
SpockのUnitTestで、Mockオブジェクトの引数をキャプチャする。
Mockitoでいうところの下記をSpockのMockingApiでやる。
Mockito.verify(hugaRepository).save(args.capture())
サンプル
HogeService と FugaRepository があるとする。
HogeServiceはビジネスロジックを担い、FugaRepositoryはDB更新を行う。
HogeService がFugaRepository を呼び出しており、FugaRepositoryをMock化しているようなケースを想定する。
public class HogeService {
private FugaRepository fugaRepository;
public HogeService(FugaRepository fugaRepository) {
this.fugaRepository = fugaRepository;
}
// テスト対象メソッド
public void update(Sample sample) {
// do Something
sample.value = "更新したい"
// DB操作はMockにしたい
fugaRepository.update(sample)
// do Something
}
}
public interface FugaRepository {
// データベースへの更新処理
int update(Sample sample);
}
captureしない場合
@Unroll
class HogeServiceSpec extends Specification {
HogeService hogeService
FugaRepository fugaRepository
def setup() {
fugaRepository = Mock()
hogeService = new HogeService(fugaRepository)
}
def "メソッドの呼び出しだけテスト"() {
setup:
// mockは常にスタブ値を返す
fugaRepository.update(*_) >> 1
def sample = new Sample
when:
hogeService.update(sample)
then:
// 呼び出されているかどうかだけテスト
1 * fugaRepository.update(_*)
}
}
一度だけcaptureする場合
Closure composition を使う。
@Unroll
class HogeServiceSpec extends Specification {
HogeService hogeService
FugaRepository fugaRepository
def setup() {
fugaRepository = Mock()
hogeService = new HogeService(fugaRepository)
}
def "メソッドの呼び出しだけテスト"() {
setup:
def sample = new Sample
// キャプチャ保持用の変数はwhenブロックより前に宣言の必要がある
def captured
when:
hogeService.update(sample)
then:
// captureする
1 * fugaRepository.update(*_) >> { sampleArg ->
// Closureのなかで参照可能
sampleArg.value == "更新したい"
captured = sample
// スタブ値を返す
return 1
}
// closureの外でも参照可能
captured.value == "更新したい"
}
}
複数回captureする場合
仮にHogeServiceが複数回fugaRepository#update()を呼んでいた場合に、それぞれをcaptureする。
やり方は同じ。
@Unroll
class HogeServiceSpec extends Specification {
HogeService hogeService
FugaRepository fugaRepository
def setup() {
fugaRepository = Mock()
hogeService = new HogeService(fugaRepository)
}
def "メソッドの呼び出しだけテスト"() {
setup:
def sample = new Sample
// キャプチャ保持用の変数はwhenブロックより前に宣言の必要がある
def capturedStock = []
when:
hogeService.update(sample)
then:
// 複数回Mockが呼ばれるようなケースを想定
(0..4).each {
1 * fugaRepository.update(*_) >> { sampleArg -> {
capturedStock.add(sampleArg)
// スタブ値を返す
return 1
}
// closureの外でも参照可能
capturedStock.size() == 5
}
}
補足
複数回呼び出しをワザワザ書いたのは、自分がClosure compositionを理解しておらずちょっとハマったから。
CaptureするClosureをCompositした場合、Mockのスタブ値が必要ならreturnしてあげないといけない。
もちろんメソッドのインターフェース次第ではあるけれども、スタブ値返さずに複数回呼び出したら動かなくなった。
参考
http://spockframework.org/spock/docs/1.0/interaction_based_testing.html#_mocking
http://www.mscharhag.com/groovy/closure-composition-in-groovy
http://stackoverflow.com/questions/22111212/how-to-do-argument-capture-with-spock-framework
http://stackoverflow.com/questions/16331230/is-there-any-way-to-do-mock-argument-capturing-in-spock