Mocked method returns null when invocations are checked as well
Asked Answered
H

2

7

I'm not sure if I'm misusing interaction based testing or if I'm doing something conceptionally wrong. I'm working on a Spring Boot Application (Kotlin) and my tests are using Spock/Groovy.

I have a class under test which invokes another service to query information. The class under test is supposed to cache results. However, I'm not able to mock a method with a return value AND check the number of invocations, because the mocked method always returns null as soon as I add the check for invocation.

I created an isolated example, which demonstrates the issue.

This is the class under test:

@Service
class DemoService(private val downstreamService: DownstreamService) {
    fun demo(id: String): String {
        val something = downstreamService.something(id)
        return something
    }
}

which is using this service:

@Service
class DownstreamService {
    fun something(id: String): String {
        return id
    }
}

and this is how my tests look like:

class DemoServiceTest extends Specification {
    def downStream = Mock(DownstreamService)
    def demoService = new DemoService(downStream)

    def "value check"() {
        given:
        def test = "my test string"
        downStream.something(test) >> "xxx"

        when:
        def actual = demoService.demo(test)

        then:
        actual == "xxx" // works
    }   

    def "invocation check"() {
        given:
        def test = "my test string"
        downStream.something(test) >> "xxx"

        when:
        demoService.demo(test)

        then:
        1 * downStream.something(test) // works
    }

    def "combined check"() {
        given:
        def test = "my test string"
        downStream.something(test) >> "xxx"

        when:
        def demo = demoService.demo(test)

        then:
        1 * downStream.something(test)
        demo == "xxx" // fails because demo is null
    }

}

The first two tests are working as expected but the last one fails because the return value is always null when I add the check for the invocation.

Hoarding answered 30/4, 2019 at 7:58 Comment(2)
See spockframework.org/spock/docs/1.3/…Fulminate
@LeonardBrünings It would help spot the information if there was a comment at the top of that example like # This will not work, the "not" in the sentence above is easy to miss... end of paragraph... visually hard to spot. Thank you.Marengo
C
8

There is a problem in combining of mocking with invocation count checking, because mock is not called then.

You can rewrite your combined test in this way to have it working:

def "combined check"() {
    given:
    def test = 'my test string'

    when:
    def demo = demoService.demo(test)

    then:
    1 * downStream.something(test) >> 'xxx'
    demo == 'xxx'
}
Casque answered 30/4, 2019 at 10:22 Comment(1)
This does not work well with Spring Webflux. I can tell my mocked class to return a Mono, but it always returns null: 1 * someService.getData(id) >> Mono.just(new Response()) will always return null if used in a then block. Using Mockito seemed to work better :(Adeno
T
1

Spock will interpret interactions based on * or >>, > operators. And the interactions in then block will be having precedence over those in given or when: block.

Since the interaction in your then block doesn't specify a response, the default response will be null.

The interactions and stubbing should go together as mentioned in the answer

You can refer to the documentation for a detailed explanation :)

  1. Where to declare interactions
  2. Combining Mocking and Stubbing
Thomajan answered 26/5, 2021 at 7:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.