LoginSignup
5
5

More than 5 years have passed since last update.

SpockのMockingApiで値をキャプチャする方法

Last updated at Posted at 2016-10-22

概要

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

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5