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.

June 2, 2017

Spocklight: Indicate Specification As Pending Feature

Sometimes we are working on a new feature in our code and we want to write a specification for it without yet really implementing the feature. To indicate we know the specification will fail while we are implementing the feature we can add the @PendingFeature annotation to our specification method. With this annotation Spock will still execute the test, but will set the status to ignored if the test fails. But if the test passes the status is set to failed. So when we have finished the feature we need to remove the annotation and Spock will kindly remind us to do so this way.

In the following example specification we use the @PendingFeature annotation:

package sample

import spock.lang.Specification
import spock.lang.PendingFeature
import spock.lang.Subject

class SampleSpec extends Specification {

    @Subject
    private final converter = new Converter()

    @PendingFeature
    void 'new feature to make String upper case'() {
        given:
        def value = 'Spock is awesome'

        expect: // This will fail as expected
        converter.upper(value) == 'SPOCK IS AWESOME'
    }

}

class Converter {
    String upper(String value) {
        value
    }
}

When we run our test in for example Gradle we get the following result:

Now let's implement the upper method:

package sample

import spock.lang.Specification
import spock.lang.PendingFeature
import spock.lang.Subject

class SampleSpec extends Specification {

    @Subject
    private final converter = new Converter()

    @PendingFeature
    void 'new feature to make String upper case'() {
        given:
        def value = 'Spock is awesome'

        expect: // This will fail no more
        converter.upper(value) == 'SPOCK IS AWESOME'
    }

}

class Converter {
    String upper(String value) {
        value.toUpperCase()
    }
}

We run the test again and now we get a failing result although our implementation of the upper method is correct:

So this tells us the @PendingFeature is no longer needed. We can remove it and the specification will pass correctly.

Written with Spock 1.1.