Why does my test not work when splitting mocking and stubbing?

73 Views Asked by At

I struggle with Spock/Groovy testing. Don't know why but in WHEN section, I cannot check number of calls and checking the value at once.. If I check one of them separatly all works fine!

This test passed:

def "should throw AppointmentNotFoundException when appointment do not exist" () {
  given:
  long nonExistingAppointmentId = 69L

  when:
  appointmentRepository.findById(nonExistingAppointmentId) >> { throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found") }
  findAppointmentQueryHandler.handle(nonExistingAppointmentId)

  then:
  thrown(AppointmentNotFoundException)
}

Also this one passed without any problem:

def "should throw AppointmentNotFoundException when appointment do not exist" () {
  given:
  long nonExistingAppointmentId = 69L

  when:
  appointmentRepository.findById(nonExistingAppointmentId) >> { throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found") }
  findAppointmentQueryHandler.handle(nonExistingAppointmentId)

  then:
  1 * appointmentRepository.findById(69L)
}

But if I try to make 1 test to check all of it then I have error

def "should throw AppointmentNotFoundException when appointment do not exist" () {
  given:
  long nonExistingAppointmentId = 69L

  when:
  appointmentRepository.findById(nonExistingAppointmentId) >> { throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found") }
  findAppointmentQueryHandler.handle(nonExistingAppointmentId)

  then:
  1 * appointmentRepository.findById(69L)
  thrown(AppointmentNotFoundException)
}

Error:

Expected exception of type 'com.bbc.anotherhospital.exceptions.AppointmentNotFoundException', but no exception was thrown
    at org.spockframework.lang.SpecInternals.checkExceptionThrown(SpecInternals.java:84)
    at org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:71)
    at com.bbc.anotherhospital.appointment.handlers.FindAppointmentQueryHandlerSpec.should throw AppointmentNotFoundException when appointment do not exist

First I think that I have problem in my code or smth.. But application works fine, tested in Postman.

Anybody have idea why is it behaving this way?

My pom:

<dependency>
  <groupId>org.apache.groovy</groupId>
  <artifactId>groovy-all</artifactId>
  <version>4.0.15</version>
  <type>pom</type>
</dependency>
<dependency>
  <groupId>org.spockframework</groupId>
  <artifactId>spock-core</artifactId>
  <version>2.4-M1-groovy-4.0</version>
  <scope>test</scope>
</dependency>
2

There are 2 best solutions below

0
tim_yates On BEST ANSWER

I had to add things so I could run your examples, but does:

class TestSpec extends Specification {

    def "should throw AppointmentNotFoundException when appointment do not exist" () {
        given:
        AppointmentRepository appointmentRepository = Mock()
        FindAppointmentQueryHandler findAppointmentQueryHandler = new FindAppointmentQueryHandler(appRepository: appointmentRepository)
        long nonExistingAppointmentId = 69L

        when:
        findAppointmentQueryHandler.handle(nonExistingAppointmentId)

        then:
        def ex = thrown(AppointmentNotFoundException)
        ex.message == "Appointment with id $nonExistingAppointmentId not found"

        1 * appointmentRepository.findById(69L) >> {
            throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found")
        }
    }

    static class FindAppointmentQueryHandler {

        AppointmentRepository appRepository

        String handle(long id) {
            appRepository.findById(id)
        }
    }

    static class AppointmentRepository {

        String findById(long id) {
            "called $id"
        }
    }

    static class AppointmentNotFoundException extends RuntimeException {

        AppointmentNotFoundException(String message) {
            super(message)
        }
    }
}

work?

2
kriegaex On

This is a Spock FAQ, also here on Stack Overflow. The Spock manual clearly explains, why what you are doing does not work:

Combining Mocking and Stubbing

Mocking and stubbing go hand-in-hand:

1 * subscriber.receive("message1") >> "ok"
1 * subscriber.receive("message2") >> "fail"

When mocking and stubbing the same method call, they have to happen in the same interaction. In particular, the following Mockito-style splitting of stubbing and mocking into two separate statements will not work:

given:
subscriber.receive("message1") >> "ok"

when:
publisher.send("message1")

then:
1 * subscriber.receive("message1")

As explained in Where to Declare Interactions, the receive call will first get matched against the interaction in the then: block. Since that interaction doesn't specify a response, the default value for the method's return type (null in this case) will be returned. (This is just another facet of Spock's lenient approach to mocking.). Hence, the interaction in the given: block will never get a chance to match.

NOTE: Mocking and stubbing of the same method call has to happen in the same interaction.

I.e., you want to use something like

1 * appointmentRepository.findById(nonExistingAppointmentId) >> {
  throw new AppointmentNotFoundException("Appointment with id " + nonExistingAppointmentId + " not found")
}

in your then: block.