[Spring Boot]メソッド呼び出し回数テストでつまづいたところ（Spockフレームワーク）

# はじめに
新卒1年目もそろそろ終わり。
Spockのフレームワークを利用する中でMockitoとSpockのモック方法で色々つまづいたので備忘録&同じような人のために共有。
ここで記載されているコードはモックにせず、実際の場ではインスタンスを生成する方が良いだろうが、あくまで例ということで…。

# 環境
- Java SDK
    - 11
- Dependency

```
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	// https://mvnrepository.com/artifact/org.spockframework/spock-core
	testCompile group: 'org.spockframework', name: 'spock-core', version: '1.3-groovy-2.5'
	// https://mvnrepository.com/artifact/org.codehaus.groovy/groovy-all
	compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.5.6', ext: 'pom'
}
```

# ソースコード
[GitHub](https://github.com/Ikeponias/SpockSample)に上げてある。

# テスト
## 通常のメソッドの呼び出し回数チェック
### Mockito
`when - thenReturn`を用いてモックの返す値を設定していく。spockComponent1Factoryはインジェクション対象となるため、`setup`で`initMocks`して`InjectMocks`アノテーションを付与しておく。`verify`箇所は`true`を記載することで回数チェックはできるようだが激しく違和感…

``` groovy:SpockComponent1FactoryTest.groovy
    @InjectMocks
    SpockComponent1Factory spockComponent1Factory

    @Mock
    SpockComponent2Factory spockComponent2Factory

    @Mock
    SpockComponent2 spockComponent2

    @Mock
    SpockComponent3 spockComponent3

    @Mock
    SpockComponent4 spockComponent4

    def setup() {
        initMocks(this)
    }

    def "GetValueFromSpockComponent4 - 呼び出し回数チェック"() {
        given:
        when(spockComponent2Factory.create(any(Integer.class))).thenReturn(spockComponent2)
        when(spockComponent2.getSpockComponent3()).thenReturn(spockComponent3)
        when(spockComponent3.getSpockComponent4()).thenReturn(spockComponent4)

        when:
        spockComponent1Factory.getValueFromSpockComponent4(0)

        then:
        verify(spockComponent2Factory, times(1)).create(any(Integer.class)) || true
    }
```

ネストされているインスタンスをモックするためには`RETURN_DEEP_STUBS`を用いる。上記のテストを書き換えると以下のようにも書ける。

``` groovy:SpockComponent1FactoryTestAlt.groovy
    @InjectMocks
    SpockComponent1Factory spockComponent1Factory

    @Mock
    SpockComponent2Factory spockComponent2Factory

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    SpockComponent2 spockComponent2

    @Mock
    SpockComponent4 spockComponent4

    def setup() {
        initMocks(this)
    }

    def "getValueFromSpockComponent - 呼び出し回数チェック改"() {
        given:
        when(spockComponent2Factory.create(any(Integer.class))).thenReturn(spockComponent2)
        when(spockComponent2.getSpockComponent3().getSpockComponent4()).thenReturn(spockComponent4)

        when:
        spockComponent1Factory.getValueFromSpockComponent4(0)

        then:
        verify(spockComponent2Factory, times(1)).create(any(Integer.class)) || true
    }
```

### Spock
同様のテストをSpockで書くと以下のようになる。実行回数を確かめたい場合、モッキングとスタビングを`then`句内で同時に行わないとテストが通らないようだ。ちなみにSpockでは`setup`で設定した振る舞いを個々のテストケースで上書きしたい場合、`then`句内で行う必要があるようだ。（参考: [Stackoverflow](https://stackoverflow.com/questions/22215929/spock-can-an-interaction-defined-in-setup-be-replaced-in-a-test-case)・[公式Document](http://spockframework.org/spock/docs/1.0/interaction_based_testing.html#_where_to_declare_interactions)）

``` groovy:SpockComponent1FactoryTestSpock.groovy
    SpockComponent1Factory spockComponent1Factory

    SpockComponent2Factory spockComponent2Factory

    SpockComponent2 spockComponent2

    SpockComponent3 spockComponent3

    SpockComponent4 spockComponent4

    def setup() {
        spockComponent2Factory = Mock(SpockComponent2Factory.class)
        spockComponent2 = Mock(SpockComponent2.class)
        spockComponent3 = Mock(SpockComponent3.class)
        spockComponent4 = Mock(SpockComponent4.class)
    }

    def "GetValueFromSpockComponent4 - 呼び出し回数チェック失敗"() {
        given:
        spockComponent3.getSpockComponent4() >> spockComponent4
        spockComponent2.getSpockComponent3() >> spockComponent3
        spockComponent2Factory.create(_ as Integer) >> spockComponent2

        when:
        spockComponent1Factory = new SpockComponent1Factory(spockComponent2Factory)
        spockComponent1Factory.getValueFromSpockComponent4(0)

        then:
        1 * spockComponent2Factory.create(_ as Integer)
    }

    def "GetValueFromSpockComponent4 - 呼び出し回数チェック成功"() {
        given:
        spockComponent3.getSpockComponent4() >> spockComponent4
        spockComponent2.getSpockComponent3() >> spockComponent3

        when:
        spockComponent1Factory = new SpockComponent1Factory(spockComponent2Factory)
        spockComponent1Factory.getValueFromSpockComponent4(0)

        then:
        1 * spockComponent2Factory.create(_ as Integer) >> spockComponent2
    }
```

Spockでもネストされているインスタンスをモックするためには一工夫必要になる。以下。

``` groovy:SpockComponent1FactoryTestSpockAlt.groovy
    SpockComponent1Factory spockComponent1Factory

    SpockComponent2Factory spockComponent2Factory

    SpockComponent2 spockComponent2

    SpockComponent4 spockComponent4

    def setup() {
        spockComponent2Factory = Mock(SpockComponent2Factory.class)
        spockComponent2 = Mock(SpockComponent2.class)
        spockComponent4 = Mock(SpockComponent4.class)
    }

    def "GetValueFromSpockComponent4 - 呼び出し回数チェック改"() {
        given:
        spockComponent2.getSpockComponent3() >> {
            Mock(SpockComponent3.class) {
                getSpockComponent4() >> spockComponent4
            }
        }

        when:
        spockComponent1Factory = new SpockComponent1Factory(spockComponent2Factory)
        spockComponent1Factory.getValueFromSpockComponent4(0)

        then:
        1 * spockComponent2Factory.create(_ as Integer) >> spockComponent2
    }
```

## nullを引数にとるメソッドの呼び出し回数チェック

実装コードではStringの引数をとるメソッドにnullを直接入れている。

``` java:SpockComponent1Factory.java
    public void nullArgumentMethodCall() {
        spockComponent2Factory.nullArgumentMethodCalled(null);
    }
```

### Mockito
`any()`でチェックしようとするとチェックできなかったため`nullable()`を用いた。

``` groovy:SpockComponent1FactoryTest.groovy
    @InjectMocks
    SpockComponent1Factory spockComponent1Factory

    @Mock
    SpockComponent2Factory spockComponent2Factory

    @Mock
    SpockComponent2 spockComponent2

    @Mock
    SpockComponent3 spockComponent3

    @Mock
    SpockComponent4 spockComponent4

    def setup() {
        initMocks(this)
    }

    def "NullArgumentMethodCall - 呼び出し回数チェック失敗"() {
        when:
        spockComponent1Factory.nullArgumentMethodCall()

        then:
        verify(spockComponent2Factory, times(1)).nullArgumentMethodCalled(any(String.class)) || true
    }

    def "NullArgumentMethodCall - 呼び出し回数チェック成功"() {
        when:
        spockComponent1Factory.nullArgumentMethodCall()

        then:
        verify(spockComponent2Factory, times(1)).nullArgumentMethodCalled(nullable(String.class)) || true
    }
```

### Spock
Spock側は特に問題なく動いた。

``` groovy:SpockComponent1FactoryTestSpock.groovy
    SpockComponent1Factory spockComponent1Factory

    SpockComponent2Factory spockComponent2Factory

    SpockComponent2 spockComponent2

    SpockComponent3 spockComponent3

    SpockComponent4 spockComponent4

    def setup() {
        spockComponent2Factory = Mock(SpockComponent2Factory.class)
        spockComponent2 = Mock(SpockComponent2.class)
        spockComponent3 = Mock(SpockComponent3.class)
        spockComponent4 = Mock(SpockComponent4.class)
    }

    def "NullArgumentMethodCall - 呼び出し回数チェック"() {
        when:
        spockComponent1Factory = new SpockComponent1Factory(spockComponent2Factory)
        spockComponent1Factory.nullArgumentMethodCall()

        then:
        spockComponent2Factory.nullArgumentMethodCalled(_ as String)
    }
}
```

# おわりに
最初にも述べたが、モックモックさせるよりも実際にインスタンスを生成した方が可読性の高いテストコードにできる可能性もあるので、考えながら書くと良さそう（自分への戒めも兼ねて）。