[JAVA] [Spring Boot] I stumbled upon a method call count test (Spock framework)

Introduction

The first year of new graduates is almost over. While using Spock's framework, I stumbled upon various mocking methods of Mockito and Spock, so I made a memorandum & shared it for similar people. It would be better to create an instance in the actual situation without mocking the code described here, but it is just an example ...

environment

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'
}

Source code

It is posted on GitHub.

test

Check the number of normal method calls

Mockito Use when --then Return to set the value returned by the mock. Since spockComponent1Factory is an injection target, ʻinitMocks is added with setup and ʻInjectMocks annotation is added. It seems that you can check the number of times by writing true in the verify part, but it feels very strange ...

SpockComponent1FactoryTest.groovy


    @InjectMocks
    SpockComponent1Factory spockComponent1Factory

    @Mock
    SpockComponent2Factory spockComponent2Factory

    @Mock
    SpockComponent2 spockComponent2

    @Mock
    SpockComponent3 spockComponent3

    @Mock
    SpockComponent4 spockComponent4

    def setup() {
        initMocks(this)
    }

    def "GetValueFromSpockComponent4 -Check the number of calls"() {
        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
    }

Use RETURN_DEEP_STUBS to mock nested instances. By rewriting the above test, it can be written as follows.

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 -Call count check break"() {
        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 A similar test written in Spock looks like this: If you want to check the number of executions, it seems that the test will not pass unless you mock and stub at the same time in the then clause. By the way, in Spock, if you want to overwrite the behavior set in setup with individual test cases, it seems that you need to do it in the then clause. (Reference: Stackoverflow-Official Document)

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 -Call count check failed"() {
        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 -Successful call count check"() {
        given:
        spockComponent3.getSpockComponent4() >> spockComponent4
        spockComponent2.getSpockComponent3() >> spockComponent3

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

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

Spock also needs some ingenuity to mock nested instances. Less than.

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 -Call count check break"() {
        given:
        spockComponent2.getSpockComponent3() >> {
            Mock(SpockComponent3.class) {
                getSpockComponent4() >> spockComponent4
            }
        }

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

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

Check the number of times a method is called that takes null as an argument

In the implementation code, null is directly put in the method that takes the argument of String.

SpockComponent1Factory.java


    public void nullArgumentMethodCall() {
        spockComponent2Factory.nullArgumentMethodCalled(null);
    }

Mockito I used nullable () because I couldn't check it with ʻany ()`.

SpockComponent1FactoryTest.groovy


    @InjectMocks
    SpockComponent1Factory spockComponent1Factory

    @Mock
    SpockComponent2Factory spockComponent2Factory

    @Mock
    SpockComponent2 spockComponent2

    @Mock
    SpockComponent3 spockComponent3

    @Mock
    SpockComponent4 spockComponent4

    def setup() {
        initMocks(this)
    }

    def "NullArgumentMethodCall -Call count check failed"() {
        when:
        spockComponent1Factory.nullArgumentMethodCall()

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

    def "NullArgumentMethodCall -Successful call count check"() {
        when:
        spockComponent1Factory.nullArgumentMethodCall()

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

Spock The Spock side worked without any problems.

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 -Check the number of calls"() {
        when:
        spockComponent1Factory = new SpockComponent1Factory(spockComponent2Factory)
        spockComponent1Factory.nullArgumentMethodCall()

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

in conclusion

As I mentioned at the beginning, it may be possible to make the test code more readable by actually creating an instance than by mocking it, so it seems better to write it while thinking (also as a commandment to yourself).

Recommended Posts

[Spring Boot] I stumbled upon a method call count test (Spock framework)
I wrote a test with Spring Boot + JUnit 5 now
I want to call a method and count the number
Make System.out a Mock with Spock Test Framework
I want to call a method of another class
How to write a unit test for Spring Boot 2
[JUnit 5 compatible] Write a test using JUnit 5 with Spring boot 2.2, 2.3
[JUnit 5] Write a validation test with Spring Boot! [Parameterization test]
[Beginner] I stumbled upon launching a project with Rails6
What I stumbled upon in the ActiveModel :: Serializer test
I haven't understood after touching Spring Boot for a month
Introducing Spring Boot2, a Java framework for web development (for beginners)
I made a simple MVC sample system using Spring Boot
Spring boot controller method memo
I tried Spring Boot introductory guide [Building a RESTful Web Service]
I made a simple search form with Spring Boot + GitHub Search API.
Sample code to unit test a Spring Boot controller with MockMvc
Spring Boot Introductory Guide I tried [Consuming a RESTful Web Service]
Let's write a test code for login function in Spring Boot