Grails Groovy

Grails 3.3 Integration Testing with Spock Mocks

It is easy to use the Spock Framework, shipped with Grails, to mock or stub a collaborator (such as a service) in our Grails unit tests.

The Testing chapter explains a bit about mocking collaborators, doWithSpring/doWithConfig callback methods, the FreshRuntime annotation to mock beans in tests — but they’re mainly for unit testing.

How about mocking beans in an integration test?

Example

What if we have a controller

class AnimalRegistrationController {
    AnimalRegistrationService animalRegistrationService

    def arrival(ArrivalCommand arrival) {

        animalRegistrationService
            .registerArrival(arrival)
            .map { ArrivalErrorMessage aem ->
                renderErrors(aem)
            }.orElse {
                render status: 200
            }
    }
}

which calls a service, which calls a repository – which might do external calls which you don’t want to happen in an integration test.

class AnimalRegistrationService {
    ArrivalRepository arrivalRepository

    Optional registerArrival(Arrival arrival) {
        arrivalRepository.registerArrival(arrival)
    }

}

Previously I wrote that Grails 3.3 has Spock 1.1 — which gave us a few new features to use such as a default answer for java.util.Optional…but it gave us more!

1. DetachedMockFactory and TestConfiguration

Now we also have a DetachedMockFactory we can use to declare mocks outside the hierarchy of a outside of a Specification, e.g. in a Spring configuration.

I got triggered by this article about Spring Integration testing, and I adjusted it to work for Grails 3.3 — which is based on Spring Boot but doesn’t quite use all the Spring annotations we’re used to in a vanilla Spring application.

So we create a configuration, specifically for testing, in src/test/groovy using a DetachedMockFactory like

import spock.mock.DetachedMockFactory
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
...

@TestConfiguration
class TestRepositoryConfig {

    private DetachedMockFactory factory = new DetachedMockFactory()

    @Bean
    ArrivalRepository arrivalRepository() {
        factory.Mock(ArrivalRepository)
    }
}

2. Integration test

We can now use the mocked bean in our Grails 3 integration test, by injecting it by type using @Autowired. We can create the expectations as usual.

@Integration
class ArrivalApiIntegrationSpec extends Specification {

    @Value('${local.server.port}')
    Integer serverPort

    @Autowired
    ArrivalRepository mockedArrivalRepository

    void "should create an arrival"() {

        given:
        1 * mockedArrivalRepository.registerArrival(_) >> {
            Optional.empty()
        }

        when:
        def response = new RestBuilder().post('http://localhost:{serverPort}/api/arrivals') {
            urlVariables([serverPort: serverPort])
            json {
                animalId = 1
                date = '2017-01-01'
            }
        } 

        then:
        response.status == 200
    }
}

2. Dependency

For the above to work, you actually have to pull in one essential spock-lang dependency.

Add it to your build.gradle

dependencies {
  ...
  testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
  testCompile 'org.spockframework:spock-spring:1.1-groovy-2.4'

Bada-bing. It’s done.

That’s it

We have now full control over our mocked beans, as if we were in a unit test.

4 comments

  1. Hello Ted,

    If I understood correctly. With the configuration you showed, ArrivalRepository will be mocked for integration/functional tests. What if you only wanted to mock it for a single test ArrivalApiIntegrationSpec but use the real implementation in other integration tests.

    thank,
    Sergio del Amo

    Like

    1. Hi Sergio,

      I haven’t been able to have Grails’ @Integration-annotated test use a specific TestConfiguration per test — in order to say what test uses the real beans and which should get mocks. The tip of Matthijs (see other comment) could work.

      Thx, Sergio.

      Like

  2. Hi Sergio,

    If you only want to override it for a single test, then simply only mock it in that test. You can inject the real AnimalRegistrationService, override the arrivalRepository with animalRegistrationService.arrivalService = Mock(ArrivalRepository). Don’t forget to clean up and “restore” the original arrivalRepository back to it’s original value in the cleanup() method. An easy way to get the original is to inject it into your test as well.

    Liked by 1 person

Comments are closed.