Ratpack is a developer friendly and productivity focused web framework. That’s quite a claim to make. We’ll explore how Ratpack’s rich testing facilities strongly support this statement.
-
Test framework Agnostic (Spock, JUnit, TestNG)
-
Core fixutres in Java 8+, first-class Groovy Support available
-
Most fixtures implement
java.lang.AutoCloseable
-
Need to either close yourself or use in
try-with-resources
-
Provides points of interaction that utilize an execute around pattern in cases where you need the fixture once.
-
link:example-01/example-01.gradle[role=include]
-
Use Gradle’s incubating Plugins feature
-
Pull in and apply Ratpack’s Gradle plugin from Gradle’s Plugin Portal
-
Pull in
'io.ratpack:ratpack-groovy-test
from Bintray
link:example-01/src/ratpack/ratpack.groovy[role=include]
link:example-01/src/main/groovy/MainClassApp.groovy[role=include]
link:example-02/src/main/groovy/ImportantHandler.groovy[role=include]
link:example-02/src/ratpack/ratpack.groovy[role=include]
-
Bind our
ImportantHandler
toGET /
def 'should render \'Very important handler\''() {
when:
HandlingResult result = GroovyRequestFixture.handle(new ImportantHandler()) {}
then:
result.bodyText == 'Very important handler // (1)
}
-
Consult the
HandlingResult
for response body
WARN: This test will fail
What happened?
Context#render(Object)
uses Ratpack’s rendering framework.
GroovyRequestFixture
does not actually serialize rendered objects to Response
of HandlingResult
.
For this test to pass you must either modify the Handler or modify the expectation:
Modify the handler:
link:example-02/src/main/groovy/ImportantSendingHandler.groovy[role=include]
Modify the expectation:
link:example-02/src/test/groovy/ImportantHandlerUnitSpec.groovy[role=include]
-
Retrieve the rendered object by type from
HandlingResult
Everday use:
link:example-02/src/main/groovy/HeaderExtractionHandler.groovy[role=include]
-
Extract HTTP header and render a response to client
link:example-02/src/test/groovy/HeaderExtractionHandlingSpec.groovy[role=include]
-
You can get a chance to configure the properties of the request to be made, can configure HTTP method, headers, request body, etc.
link:example-02/src/main/groovy/ProfileLoadingHandler.groovy[role=include]
-
Extract role from request header, defaulting to 'guest'
-
Extract a String from the context registry
-
Delegate to the next Handler in the chain and pass a new single Registry with a newly constructed Profile object
We can make use of RequestFixture
to populate the Registry with any entries our stand-alone Handler may be expecting, such as a token in the form of a String.
link:example-02/src/test/groovy/ProfileLoadingHandlingSpec.groovy[role=include]
-
Use
RequestFixture#header
to add Headers to the HTTP Request -
Use
RequestFixture#registry
to add aString
to the Context registry -
Consult the HandlingResponse to ensure that the context was populated with a
Profile
object and that it meets our expectations
Let’s put our ProfileLoadingHandler
in a chain with a dummy Map renderer:
link:example-02/src/test/groovy/ProfileLoadingHandlingSpec.groovy[role=include]
GroovyEmbeddedApp
represents an isolated subset of functionality that stands up a full Ratpack server.
It represents a very bare server that binds to an ephemeral port and has no base directory by default.
GroovyEmbeddedApp
is also AutoCloseable
.
If you plan on making more than a few interactions it may help to grab a TestHttpClient
from the server, otherwise you can make use of EmbeddedApp#test(TestHttpClient)
which will ensure that the EmbeddedApp
is shut down gracefully.
Javadocs for Ratpack are 100% tested and make use of EmbeddedApp
to demonstrate functionality.
The EmbeddedApp
is also useful in creating a test fixture that represents some network based resource that returns canned or contrived responses.
link:example-03/src/test/groovy/EmbeddedAppSpec.groovy[role=include]
-
Creates a full Ratpack server with a single handler
-
Ratpack provides us with a
TestHttpClient
that is configured to submit requests toEmbeddedApp
. When the closure is finished executing Ratpack will take care of cleaning up theEmbeddedApp
.
For testing, Ratpack provides TestHttpClient
which is a blocking, synchronous http client for making requests against a running ApplicationUnderTest
. This is intentionally designed in order to make testing deterministic and predictable.
link:example-03/src/test/groovy/EmbeddedAppSpec.groovy[role=include]
-
Create
GroovyEmbeddedApp
from a chain -
Retrieve a configured
TestHttpClient
for making requests against theEmbeddedApp
-
Make some assertions about the application as described by the chain
-
Have Spock invoke
EmbeddedApp#close
to gracefully shutdown the server.
The TestHttpClient
has some basic support for manipulating request configuration as well as handling redirects and cookies.
link:example-03/src/test/groovy/EmbeddedAppSpec.groovy[role=include]
-
Create sample app that reads and writes cookies
-
Issue requests that ensures cookie setting/expiring and redirect functionality
Ratpack is asynchronous and non-blocking from the ground up. This means that not only is Ratpack’s api asynchronous but it expects that your code should be asynchronous as well.
Let’s say we have a ProfileService
that’s responsible for retrieving `Profile`s:
link:example-04/src/main/groovy/ProfileService.groovy[role=include]
If you were to test this Service without any assistance from Ratpack you will run into the well known UnmanagedThreadException
:
ratpack.exec.UnmanagedThreadException: Operation attempted on non Ratpack managed thread
ExecHarness
is the utility that Ratpack provides to test any kind of asynchronous behavior.
Unsurprisingly ExecHarness
is also an AutoCloseable
.
It utilizes resources that manage an EventLoopGroup
and an ExecutorService
so it’s important to make sure these resources get properly cleaned up.
link:example-04/src/test/groovy/ProfileServiceSpec.groovy[role=include]
-
Create an
ExecHarness
and tell Spock to clean up when we are finished -
Use
ExecHarness#yield
to wrap all of our service calls so that our Promises and Operations can be resolved on a Ratpack managed thread.
GroovyRatpackMainApplicationUnderTest
-
For testing
ratpack.groovy
backed applications
link:example-01/src/test/groovy/HelloWorldSpec.groovy[role=include]
MainClassApplicationUnderTest
-
For testing class backed applications
link:example-01/src/test/groovy/HelloWorldSpec.groovy[role=include]
Our sample Ratpack application for testing:
link:example-05/src/ratpack/ratpack.groovy[role=include]
-
Pull configuration from System properties
-
Create an ApiConfig object and put into the registry
-
Bind
ConfService
using Guice -
Use
ConfService
to retrieve list of awesome Groovy Conferences
link:example-05/src/main/groovy/ApiConfig.groovy[role=include]
Simple object to contain our configuration data related to an API
link:example-05/src/main/groovy/ConfService.groovy[role=include]
-
Receive
ApiConfig
andHtpClient
from Guice -
Define an asynchronous service method to retrieve data from remote service
We can take advantage of system properties to change how the Ratpack application configures its services.
link:example-05/src/test/groovy/FunctionalSpec.groovy[role=include]
-
Create our
ApplicationUnderTest
and tell Spock to clean up when we’re done -
Retrieve
TestHttpClient
and make use of@Delegate
to make tests very readable -
Create a simple service that response with a comma separated list of Groovy Conferences
-
Set system property to point to our stubbed service
-
Write a simple test to assure that our Ratpack app can make a succcessful call to the remote api
Impositions
allow a user to provide overrides to various aspects of the Ratpack application bootstrap phase.
-
ServerConfigImposition
allows to override server configuration -
BindingsImposition
allows to provide Guice binding overrides -
UserRegistryImposition
allows you to provide alternatives for items in the registry
link:example-05/src/test/groovy/ImpositionSpec.groovy[role=include]
-
Override
addImpositions
method to provide aUserRegistryImposition
that supplies our own dumb implementation ofConfService
that does not need to make any network connections
Authored by Luke Daley; originally for Grails
Used to serialize commands to be executed on the ApplicationUnderTest
link:example-06/example-06.gradle[role=include]
Here we add a test compile dependency on io.ratpack:ratpack-remote-test
which includes a dependency on remote-control
link:example-06/src/test/groovy/RemoteControlSpec.groovy[role=include]
-
We use
BindingsImposition
here to add a hook into the runningApplicationUnderTest
that allows us to run remote code on the server -
We tell
RemoteControl
not to complain if the result of the command is not Serializable -
We use remote control here to grab the
ProfileService
and manually add a profile
A utility that provides a nice way to interact with files that would provide the basis of a base directory for Ratpack applications.
It is also an AutoCloseable
so you’ll need to make sure to clean up after use.
link:example-07/src/test/groovy/EphemeralSpec.groovy[role=include]