Search

Dark theme | Light theme
Showing posts with label Ratpacked:Testing. Show all posts
Showing posts with label Ratpacked:Testing. Show all posts

June 7, 2017

Ratpacked: Assert No Exceptions Are Thrown With RequestFixture

Writing unit tests for our handlers in Ratpack is easy with RequestFixture. We invoke the handle method and use a Handler or Chain we want to test as argument. We can provide extra details on the fixture instance with a second argument, for example adding objects to the registry or setting the request method. The handle method returns a HandlingResult object. This object has the method exception that we can use to see if an exception occurred in our code under test. The method throws a HandlerExceptionNotThrownException if the expected exception doesn't occurr.

In the following example we have two feature methods to check if an exception occurred or not:

package sample

import ratpack.handling.Context
import ratpack.handling.Handler
import ratpack.test.handling.RequestFixture
import spock.lang.Specification

class HandlerSpec extends Specification {

    def 'check exception is thrown'() {
        given:
        def result = RequestFixture.handle new SampleHandler(true), Action.noop()

        expect:
        result.exception(Exception).message == 'Sample exception'
    }

    def 'check no exception is thrown'() {
        given:
        def result = RequestFixture.handle new SampleHandler(false), Action.noop()
        
        when:
        result.exception(Exception)

        then:
        thrown(HandlerExceptionNotThrownException)
    }
    
}

class SampleHandler implements Handler {
    
    /**
     * Indicate if we need to create an 
     * error with an exception or not.
     */
    private final boolean throwException = false

    SampleHandler(final boolean throwException) {
        this.throwException = throwException
    }

    @Override
    void handle(final Context ctx) throws Exception {
        if (throwException) {
            // Throw a sample exception.
            ctx.error(new Exception('Sample exception'))
            ctx.response.send()
        } else {
            // No exceptions.
            ctx.response.send('OK')
        }
    }
    
}

Instead of using the exception method of HandlingResult we can add a custom ServerErrorHandler to the fixture registry. Exceptions are handled by the error handler and we can check if an exception occurred or not via the error handler. In the following code we use a custom error handler:

package sample

import ratpack.error.ServerErrorHandler
import ratpack.handling.Context
import ratpack.handling.Handler
import ratpack.test.handling.RequestFixture
import spock.lang.Specification

class HandlerSpec extends Specification {

    /**
     * Error handler to capture exceptions.
     */
    private specErrorHandler = new SpecErrorHandler()

    /**
     * Add error handler as {@link ServerErrorHandler}
     * implementation to the fixture registry.
     */
    private fixtureErrorHandler = { fixture ->
        fixture.registry.add ServerErrorHandler, specErrorHandler
    }
    
    def 'check exception is thrown'() {
        when:
        RequestFixture.handle new SampleHandler(true), fixtureErrorHandler

        then:
        specErrorHandler.exceptionThrown()
        
        and:
        specErrorHandler.throwable.message == 'Sample exception'
    }

    def 'check no exception is thrown'() {
        when:
        RequestFixture.handle new SampleHandler(false), fixtureErrorHandler

        then:
        specErrorHandler.noExceptionThrown()
    }
    
}

class SampleHandler implements Handler {
    
    /**
     * Indicate if we need to create an 
     * error with an exception or not.
     */
    private final boolean throwException = false

    SampleHandler(final boolean throwException) {
        this.throwException = throwException
    }

    @Override
    void handle(final Context ctx) throws Exception {
        if (throwException) {
            // Throw a sample exception.
            ctx.error(new Exception('Sample exception'))
            ctx.response.send()
        } else {
            // No exceptions.
            ctx.response.send('OK')
        }
    }
    
}

/**
 * Simple implementation for {@link ServerErrorHandler}
 * where we simply store the original exception and 
 * add utility methods to determine if an exception is
 * thrown or not.
 */
class SpecErrorHandler implements ServerErrorHandler {
    
    /**
     * Store original exception.
     */
    private Throwable throwable

    /**
     * Store exception in {@link #throwable} and 
     * set response status to {@code 500}.
     * 
     * @param context Context for request.
     * @param throwable Exception thrown in code.
     * @throws Exception Something goes wrong.
     */
    @Override
    void error(final Context context, final Throwable throwable) throws Exception {
        this.throwable = throwable
        context.response.status(500)
    }

    /**
     * @return {@code true} if error handler is invoked, {@code false} otherwise.
     */
    boolean exceptionThrown() {
        throwable != null
    }

    /**
     * @return {@code true} if error handler is not invoked, {@code false} otherwise.
     */
    boolean noExceptionThrown() {
        !exceptionThrown()
    }
    
}

Written with Ratpack 1.4.5.

March 8, 2017

Ratpacked: Override Registry Objects With Mocks In Integration Specifications

Testing a Ratpack application is not difficult. Ratpack has excellent support for writing unit and integration tests. When we create the fixture MainClassApplicationUnderTest we can override the method addImpositions to add mock objects to the application. We can add them using the ImpositionsSpec object. When the application starts with the test fixture the provided mock objects are used instead of the original objects. For a Groovy based Ratpack application we can do the same thing when we create the fixture GroovyRatpackMainApplicationUnderTest.

We start with a simple Java Ratpack application. The application adds an implementation of a NumberService interface to the registry. A handler uses this implementation for rendering some output.

// File: src/main/java/mrhaki/ratpack/RatpackApplication.java
package mrhaki.ratpack;

import ratpack.registry.Registry;
import ratpack.server.RatpackServer;

public class RatpackApplication {
    public static void main(String[] args) throws Exception {
        RatpackServer.start(server -> server
                // Add a implementation of NumberService interface
                // to the registry, so it can be used by the handler.
                .registry(Registry.single(NumberService.class, new NumberGenerator()))
                
                // Register a simple handler to get the implementation
                // of the NumberService interface, invoke the give() method
                // and render the value.
                .handler(registry -> ctx -> ctx
                        .get(NumberService.class).give()
                        .then(number -> ctx.render(String.format("The answer is: %d", number)))));
    }
}

The NumberService interface is not difficult:

// File: src/main/java/mrhaki/ratpack/NumberService.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

public interface NumberService {
    Promise<Integer> give();
}

The implementation of the NumberService interface returns a random number:

// File: src/main/java/mrhaki/ratpack/NumberGenerator.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

import java.util.Random;

public class NumberGenerator implements NumberService {
    @Override
    public Promise<Integer> give() {
        return Promise.sync(() -> new Random().nextInt());
    }
}

To test the application we want to use a mock for the NumberService interface. In the following specification we override the addImpositions method of the MainClassApplicationUnderTest class:

// File: src/test/groovy/mrhaki/ratpack/RatpackApplicationSpec.groovy
package mrhaki.ratpack

import ratpack.exec.Promise
import ratpack.impose.ImpositionsSpec
import ratpack.impose.UserRegistryImposition
import ratpack.registry.Registry
import ratpack.test.MainClassApplicationUnderTest
import ratpack.test.http.TestHttpClient
import spock.lang.AutoCleanup
import spock.lang.Specification

/**
 * Integration test for the application.
 */
class RatpackApplicationSpec extends Specification {
    
    /**
     * Mock implementation for the {@link NumberService}.
     */
    private final NumberService mockNumberService = Mock()

    /**
     * Setup {@link RatpackApplication} for testing and provide
     * the {@link #mockNumberService} instance to the Ratpack registry.
     */
    @AutoCleanup
    private aut = new MainClassApplicationUnderTest(RatpackApplication) {
        @Override
        protected void addImpositions(final ImpositionsSpec impositions) {
            // Set implementation of NumberService interface to
            // our mock implementation for the test.
            impositions.add(
                    UserRegistryImposition.of(
                            Registry.single(NumberService, mockNumberService)))
        }
    }

    /**
     * Use HTTP to test our application.
     */
    private TestHttpClient httpClient = aut.httpClient
    
    void 'render output with number'() {
        when:
        final response = httpClient.get()
        
        then:
        // Our mock should get invoked once and we return 
        // the fixed value 42 wrapped in a Promise.
        1 * mockNumberService.give() >> Promise.sync { 42 }

        and:
        response.statusCode == 200
        response.body.text == 'The answer is: 42'
    }
}

Written with Ratpack 1.4.5.

March 6, 2017

Ratpacked: Render Optional Type Instance

Ratpack uses renderers to render objects with the render method of the Context class. Ratpack has several renderers that are available automatically. One of those renderers is the OptionalRenderer. When we want to render an Optional object this renderer is selected by Ratpack. If the Optional instance has a value the value is passed to the render method. If the value is not present a 404 client error is returned.

In the following example application we have a RecipeRepository class with a findRecipeByName method. This method returns Promise<Optional<Recipe>>:

// File: src/main/java/mrhaki/ratpack/RecipeRepository.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

import java.util.Optional;

public interface RecipeRepository {
    Promise<Optional<Recipe>> findRecipeByName(final String name);
}

We have a Handler that will use the findRecipeByName method and then render the Optional<Recipe> object. The following example application shows the handler implementation:

// File: src/main/java/mrhaki/ratpack/Application.java
package mrhaki.ratpack;

import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.Handler;
import ratpack.registry.RegistrySpec;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class Application {

    public static void main(String[] args) throws Exception {
        new Application().startServer();
    }
    
    void startServer() throws Exception {
        RatpackServer.start(server -> server
                .registryOf(registry())
                .handlers(chain()));
    }
    
    private Action<RegistrySpec> registry() {
        return registry -> registry
                .add(new RecipeRenderer())
                .add(RecipeRepository.class, new RecipesList());
    }

    private Action<Chain> chain() {
        return chain -> chain.post("recipe", recipeHandler());
    }

    private Handler recipeHandler() {
        return ctx -> ctx
                .parse(RecipeRequest.class)
                .flatMap(recipeRequest -> ctx
                        .get(RecipeRepository.class)
                        .findRecipeByName(recipeRequest.getName()))
                .then((Optional<Recipe> optionalRecipe) -> ctx.render(optionalRecipe));
    }

}

The application also uses a custom RecipeRenderer. This renderer is used when the Optional<Recipe> has a value:

// File: src/main/java/mrhaki/ratpack/RecipeRenderer.java
package mrhaki.ratpack;

import ratpack.handling.Context;
import ratpack.render.RendererSupport;

import static ratpack.jackson.Jackson.json;

public class RecipeRenderer extends RendererSupport<Recipe> {
    @Override
    public void render(final Context ctx, final Recipe recipe) throws Exception {
        ctx.render(json(recipe));
    }
}

Let's write a specification where we can test that a client error with status code 404 is returned when the Optional is empty. Otherwise the actual value is rendered:

// File: src/test/groovy/mrhaki/ratpack/ApplicationSpec.groovy
package mrhaki.ratpack

import groovy.json.JsonSlurper
import ratpack.exec.Promise
import ratpack.http.MediaType
import ratpack.impose.ImpositionsSpec
import ratpack.impose.UserRegistryImposition
import ratpack.registry.Registry
import ratpack.test.MainClassApplicationUnderTest
import spock.lang.Specification
import spock.lang.Subject

import static groovy.json.JsonOutput.toJson

class ApplicationSpec extends Specification {
    
    private RecipeRepository recipeMock = Mock()
    
    @Subject
    private aut = new MainClassApplicationUnderTest(Application) {
        @Override
        protected void addImpositions(final ImpositionsSpec impositions) {
            // Add mock for RecipeRepository.
            impositions.add(UserRegistryImposition.of(Registry.of { registry ->
                registry.add(RecipeRepository, recipeMock)
            }))
        }
    }
    
    private httpClient = aut.httpClient
    
    void 'response status 404 when Optional<Recipe> is empty'() {
        when:
        def response = httpClient.requestSpec { requestSpec ->
            requestSpec.headers.set 'Content-type', MediaType.APPLICATION_JSON
            requestSpec.body { body ->
                body.text(toJson(name: 'sushi'))
            }
        }.post('recipe')
        
        then:
        1 * recipeMock.findRecipeByName('sushi') >> Promise.value(Optional.empty())
        
        and:
        response.statusCode == 404
    }

    void 'render Recipe when Optional<Recipe> is not empty'() {
        when:
        def response = httpClient.requestSpec { requestSpec ->
            requestSpec.headers.set 'Content-type', MediaType.APPLICATION_JSON
            requestSpec.body { body ->
                body.text(toJson(name: 'macaroni'))
            }
        }.post('recipe')

        then:
        1 * recipeMock.findRecipeByName('macaroni') >> Promise.value(Optional.of(new Recipe('macaroni')))

        and:
        response.statusCode == 200
        
        and:
        def recipe = new JsonSlurper().parseText(response.body.text)
        recipe.name == 'macaroni'
    }

}

Written with Ratpack 1.4.5.

March 2, 2017

Ratpacked: Using Spring Cloud Contract To Implement Server

Spring Cloud Contract is a project that allows to write a contract for a service using a Groovy DSL. In the contract we describe the expected requests and responses for the service. From this contract a stub is generated that can be used by a client application to test the code that invokes the service. Spring Cloud Contract also generates tests based on the contract for the service implementation. Let's see how we can use the generated tests for the service implementation for a Ratpack application.

Spring Cloud Contract comes with a Gradle plugin. This plugin adds the task generateContractTests that creates tests based on the contract we write. There are also tasks to create the stub for a client application, but here we focus on the server implementation. In the following Gradle build file for our Ratpack application we use the Spring Cloud Contract Gradle plugin. We configure the plugin to use Spock as framework for the generated tests.

buildscript {
    ext {
        verifierVersion = '1.0.3.RELEASE'
    }
    repositories {
        jcenter()
    }
    dependencies {
        // We add the Spring Cloud Contract plugin to our build.
        classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${verifierVersion}"
    }
}

plugins {
    id 'io.ratpack.ratpack-java' version '1.4.5'
    id 'com.github.johnrengelman.shadow' version '1.2.4'
    
    // The Spring Cloud Contract plugin relies on
    // the Spring dependency management plugin to 
    // resolve the dependency versions.
    id 'io.spring.dependency-management' version '1.0.0.RELEASE'
}

apply plugin: 'spring-cloud-contract'

repositories {
    jcenter()
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:${verifierVersion}"
    }
}

dependencies {
    runtime 'org.slf4j:slf4j-simple:1.7.24'

    testCompile 'org.codehaus.groovy:groovy-all:2.4.9'
    testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
    testCompile 'org.spockframework:spock-spring:1.0-groovy-2.4'
    testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
    testCompile 'commons-logging:commons-logging:1.2'
}

mainClassName = 'mrhaki.sample.PirateApp'

assemble.dependsOn shadowJar

/**************************************************************
 * Configure Spring Cloud Contract plugin
 *************************************************************/
contracts {
    // Of course we use Spock for the generated specifications.
    // Default is JUnit.
    targetFramework = 'Spock'

    // With explicit testMode real HTTP requests are sent
    // to the application from the specs.
    // Default is MockMvc for Spring applications.
    testMode = 'Explicit'

    // Base class with setup for starting the Ratpack
    // application for the generated specs.
    baseClassForTests = 'mrhaki.sample.BaseSpec'

    // Package name for generated specifications.
    basePackageForTests = 'mrhaki.sample'
}

It is time to write some contracts. We have a very basic example, because we want to focus on how to use Spring Cloud Contract with Ratpack and we don't want to look into all the nice features of Spring Cloud Contract itself. In the directory src/test/resources/contracts/pirate we add a contract for the endpoint /drink:

// File: src/test/resources/contracts/pirata/drink.groovy
package contracts.pirate

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    request {
        method 'GET'
        urlPath '/drink', {
            queryParameters {
                parameter 'name': $(consumer(regex('[a-zA-z]+')), producer('mrhaki'))
            }
        }
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 200
        body([response: "Hi-ho, ${value(consumer('mrhaki'), producer(regex('[a-zA-z]+')))}, ye like to drink some spiced rum!"])
        headers {
            contentType(applicationJson())
        }
    }
}

We add a second contract for an endpoint /walk:

// File: src/test/resources/contracts/pirata/walk_the_plank.groovy
package contracts.pirate

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    request {
        method 'POST'
        urlPath '/walk'
        body([name: $(consumer(regex('[a-zA-z]+')), producer('mrhaki'))])
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 200
        body([response: "Ay, matey, ${value(consumer('mrhaki'), producer(regex('[a-zA-z]+')))}, walk the plank!"])
        headers {
            contentType(applicationJson())
        }
    }
}

The last step for generating the Spock specifications based on these contracts is to define a base class for the tests. Inside the base class we use Ratpack's support for functional testing. We define our application with MainClassApplicationUnderTest and use the getAddress method to start the application and to get the port that is used for the application. The generated specifications rely on RestAssured to invoke the HTTP endpoints, so we assign the port to RestAssured:

// File: src/test/groovy/mrhaki/sample/BaseSpec.groovy
package mrhaki.sample

import com.jayway.restassured.RestAssured
import ratpack.test.MainClassApplicationUnderTest
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

abstract class BaseSpec extends Specification {
    
    @Shared
    @AutoCleanup
    def app = new MainClassApplicationUnderTest(PirateApp)
    
    def setupSpec() {
        final URI address = app.address
        RestAssured.port = address.port
    }
}

We can write the implementation for the PirateApp application and use Gradle's check tasks to let Spring Cloud Contract generate the specification and run the specifications. The specification that is generated can be found in build/generated-test-sources and looks like this:

// File: build/generated-test-sources/contracts/mrhaki/sample/PirateSpec.groovy
package mrhaki.sample

import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath

import static com.jayway.restassured.RestAssured.given
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson

class PirateSpec extends BaseSpec {

    def validate_drink() throws Exception {
        given:
        def request = given()
                .header("Content-Type", "application/json")

        when:
        def response = given().spec(request)
                              .queryParam("name", "mrhaki")
                              .get("/drink")

        then:
        response.statusCode == 200
        response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/json.*')
        and:
        DocumentContext parsedJson = JsonPath.parse(response.body.asString())
        assertThatJson(parsedJson).field("response").matches(
                "Hi-ho, [a-zA-z]+, ye like to drink some spiced rum!")
    }

    def validate_walk_the_plank() throws Exception {
        given:
        def request = given()
                .header("Content-Type", "application/json")
                .body('''{"name":"mrhaki"}''')

        when:
        def response = given().spec(request)
                              .post("/walk")

        then:
        response.statusCode == 200
        response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/json.*')
        and:
        DocumentContext parsedJson = JsonPath.parse(response.body.asString())
        assertThatJson(parsedJson).field("response").matches("Ay, matey, [a-zA-z]+, walk the plank!")
    }

}

If we run Gradle's check task we can see the Spring Cloud Contract plugin tasks are executed as well:

$ gradle check
:copyContracts
:generateContractTests
:compileJava
:compileGroovy NO-SOURCE
:processResources NO-SOURCE
:classes
:compileTestJava NO-SOURCE
:compileTestGroovy
:processTestResources
:testClasses
:test
:check

BUILD SUCCESSFUL

Total time: 5.749 secs

The code for the complete application is on Github.

Written with Ratpack 1.4.5 and Spring Cloud Contract 1.0.3.RELEASE.

June 15, 2016

Ratpacked: Stub External HTTP Service

Suppose we have a piece of code that uses an external HTTP service. If we write a test for this code we can invoke the real HTTP service each time we execute the tests. But it might be there is a request limit for the service or the service is not always available when we run the test. With Ratpack it is very, very easy to write a HTTP service that mimics the API of the external HTTP service. The Ratpack server is started locally in the context of the test and we can write extensive tests for our code that uses the HTTP service. We achieve this using the Ratpack EmbeddedApp or GroovyEmbeddedApp class. With very little code we configure a server that can be started and respond to HTTP requests.

In our example project we have a class GeocodeService that uses the external service MapQuest Open Platform Web Services. We use the HTTP Requests library to make a HTTP request and transform the response to an object:

// File: src/main/groovy/mrhaki/geocode/GeocodeService.groovy
package mrhaki.geocode

import com.budjb.httprequests.HttpClient
import com.budjb.httprequests.HttpResponse

class GeocodeService {

    private final HttpClient httpClient
    private final GeocodeConfig config

    GeocodeService(
            final HttpClient httpClient,
            final GeocodeConfig config) {

        this.httpClient = httpClient
        this.config = config
    }

    Location getLocation(final Double latitude, final Double longitude) {
        // Request location details for given latitude and longitude
        // using a external HTTP service.
        final HttpResponse response =
                httpClient.get {
                    uri = "${config.uri}geocoding/v1/reverse".toURI()

                    addQueryParameter 'key', config.apiKey
                    addQueryParameter 'location', [latitude, longitude].join(',')
                }
        
        // Transform JSON result to Map.
        final Map responseMap = response.getEntity(Map)
        
        // Find location specific details in the response.
        final Map location = responseMap.results[0].locations[0]
        
        // Create new Location object.
        new Location(street: location.street, city: location.adminArea5)
    }
}

The host name and key we need to make a request are set via the GeocodeConfig class:

// File: src/main/groovy/mrhaki/geocode/GeocodeConfig.groovy
package mrhaki.geocode

class GeocodeConfig {
    String apiKey
    String uri
}

And finally a simple POGO to store the location details:

// File: src/main/groovy/mrhaki/geocode/Location.groovy
package mrhaki.geocode

import groovy.transform.Immutable

@Immutable
class Location {
    String street
    String city
}

To access the real MapQuest API service we would set the host and key in the GeocodeConfig object and we get results from the web service. Now we want to write a Spock specification and instead of accessing the real API, we implement the MapQuest API with Ratpack.

// File: src/test/groovy/mrhaki/geocode/GeocodeServiceSpec.groovy
package mrhaki.geocode

import com.budjb.httprequests.HttpClient
import com.budjb.httprequests.HttpClientFactory
import com.budjb.httprequests.jersey2.JerseyHttpClientFactory
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.test.CloseableApplicationUnderTest
import spock.lang.AutoCleanup
import spock.lang.Specification
import spock.lang.Subject

import static ratpack.jackson.Jackson.json

class GeocodeServiceSpec extends Specification {

    @AutoCleanup
    private CloseableApplicationUnderTest mapQuestApi = mapQuestApiServer()

    @Subject
    private GeocodeService geocodeService

    def setup() {
        final HttpClientFactory httpClientFactory = new JerseyHttpClientFactory()
        final HttpClient httpClient = httpClientFactory.createHttpClient()

        // Get address and port for Ratpack
        // MapQuest API server.
        final String serverUri = mapQuestApi.address.toString()

        final GeocodeConfig config =
                new GeocodeConfig(
                        apiKey: 'secretApiKey',
                        uri: serverUri)

        geocodeService = new GeocodeService(httpClient, config)
    }

    def "get location from given latitude and longitude"() {
        when:
        final Location location = geocodeService.getLocation(52.0298141, 5.096626)

        then:
        with(location) {
            street == 'Marconibaan'
            city == 'Nieuwegein'
        }
    }

    private GroovyEmbeddedApp mapQuestApiServer() {
        // Create a new Ratpack server, with
        // a single handler to mimic MapQuest API.
        GroovyEmbeddedApp.fromHandlers {
            get('geocoding/v1/reverse') {
                // Extra check to see if required parameters
                // are set. This is optional, we could also
                // ignore them in this stub implementation.
                if (!request.queryParams.key) {
                    response.status = 500
                    response.send('Query parameter "key" not set')
                    return
                }
                if (!request.queryParams.location) {
                    response.status = 500
                    response.send('Query parameter "location" not set')
                    return
                }

                // Create a response, like the real API would do.
                // In this case a fixed value, but we could do 
                // anything here, for example different responses, based
                // on the location request parameter. 
                final Map response = 
                    [results: [
                        [locations: [
                            [street: 'Marconibaan', adminArea5: 'Nieuwegein']]]]]
                render(json(response))
            }
        }
    }
}

To run our test we only have to add Ratpack as a dependency to our project. The following example Gradle build file is necessary for this project:

// File: build.gradle
apply plugin: 'groovy'

repositories {
    jcenter()
}

dependencies {
    compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.7'
    
    // HttpRequests library to access HTTP services.
    compile group: 'com.budjb', name: 'http-requests-jersey2', version: '1.0.1'

    testCompile group: 'org.spockframework', name: 'spock-core', version: '1.0-groovy-2.4'
    
    // Include this Ratpack dependency for the GroovyEmbeddedApp class,
    // we need in the specification.
    testCompile group: 'io.ratpack', name: 'ratpack-groovy-test', version: '1.3.3'
}

Ratpack makes it so easy to create a new HTTP service and in this case use it in a test.

Written with Ratpack 1.3.3.