Search code examples
groovyjunitmockitoproject-reactorspock

How to get Spock to match in Reactor similar to Mockito?


I am trying to test a class that will call another web service to fetch data if a null value is provided. My JUnit + Mockito test works great, but I cannot get my Spock test to match in the then: block. The error returned during the Spock test is:

Suppressed: java.lang.NullPointerException: Cannot invoke method map() on null object

Which is because my mocked method is not matching, and therefore returns null.

Spock test (not working)


class MySpec extends Specfication {

    def mockCollaboratingService = Mock(CollaboratingService)
    def service = new MyService(collaboratingService: mockCollaboratingService)

    def "should call another service if the provided start value equals null"() {
        given:
        def id = 123
        def startDate = null

        when: "the service is called"
        def result = service.getTransactions(id, startDate)

        then:
        1 * mockCollaboratingService.getData(id) >> Mono.just(new SomeMockResponse(key: "123"))

        StepVerifier
            .create(result)
            .consumeNextWith({
                // assertions here
            })
            .verifyComplete()
    }
}

JUnit + Mockito (working)


class AnotherSpec {
    def mockCollaboratingService = Mockito.mock(MockCollaboratingService)
    def service = new MyService(collaboratingService: mockCollaboratingService)

    @Test
    @DisplayName("should call the statement service if the given start date is null")
    void shouldCallStatementServiceIfStartDateEqualsNull() {
        def id = 123
        def startDate = null

        // and some fake data returned from the api
        Mockito.when(mockCollaboratingService.getData(id)).thenReturn(Mono.just(new SomeMockResponse(key: "123")))

        //when
        def result = service.getTransactions(id, null)

        //then
        StepVerifier
                .create(result)
                .consumeNextWith({
                    Mockito.verify(mockStatementService, Mockito.times(1)).getLastStatement(enterpriseToken)
                    assert it.transactionId == 123
                })
                .verifyComplete()
    }
}


Solution

  • Spock handles mocking differently than mockito, have a look at combining mocking and stubbing. Furthermore, all interactions must be finished before the then block, as Spock verifies them as first thing. With reactor, it is actually StepVerifier that is executing the code. The line def result = service.getTransactions(id, startDate) only creates a cold Mono/Flux, but doesn't to anything.

    So, you have to reorder your tests slightly.

    class MySpec extends Specfication {
    
        def mockCollaboratingService = Mock(CollaboratingService)
        def service = new MyService(collaboratingService: mockCollaboratingService)
    
        def "should call another service if the provided start value equals null"() {
            given:
            def id = 123
            def startDate = null
    
            when: "the service is called"
            def result = service.getTransactions(id, startDate)
    
            and: "code is executed"
            StepVerifier
                .create(result)
                .consumeNextWith({
                    // no explicit mock assertions necessary
                    assert it.transactionId == 123
                })
                .verifyComplete()
    
            then:
            1 * mockCollaboratingService.getData(id) >> Mono.just(new SomeMockResponse(key: "123"))
    
        }
    }