Skip to main content

Testing

Groovy Spock

We're big believers in testing our code, both for correctness, as well as to ensure that changes don't unintentionally break existing contracts unintentionally. For example, we rely heavily on the Spock framework for our backend service tests, and see a lot of benefit from it's conciseness, built-in mocking framework, and the fact that it uses Groovy.

We also strive for very high-quality code, with the belief that quality code is easier to maintain, easier to understand, and has fewer bugs. To help keep the quality bar high. For instance we have an automated style checker (Checkstyle) in our Maven-based projects with rules that should catch most of the common style issues.

Servlet Testing

tip

The design of athena-core tests, servlet tests in particular, draws extensively from fili

One noticeable deviation is that since some of Fili's classes have made it possible for themselves to be mutable, which Athena doesn't do, the stubbing is defined not on these classes, but on ApplicationState, which is a modified adaption of Fili ApplicationState

Servlet-related testing is carried out using Jersey Test Framework.

Error loading class-diagram.png

Each ***ServletSpec.groovy follows the following pattern to setup, run, and shutdown tests:

1. Initializing ApplicationState

Test specs initializes test data and mocking through ApplicationState in setup()

def setup() {
ApplicationState applicationState = new ApplicationState();
applicationState.metadataByFileId = ...
applicationState.queryFormatter = ...
applicationState.mutationFormatter = ...

...
}
  • applicationState.metadataByFileId initializes GraphQL DataFetcher data
  • queryFormatter transforms a (file ID, metadata field list) pair to a native GraphQL query
  • mutationFormatter transforms a (file ID, metadata object) pair to a native GraphQL query that persists a new metadata to database (or just in-memory that usually suffices in testing scenarios)

2. Creating Test Harness

def setup() {
...

jerseyTestBinder = new JerseyTestBinder(true, applicationState, ***Servlet.class)
}

Executing the statement above will start a Grizzly container. After that all Athena endpoints are ready to receive test requests.

tip

When writing tests for FileServlet, make sure MultiPartFeature.class is also passed in as a resource class since the file uploading involves a separate Jersey component enabled by it. For example:

jerseyTestBinder = new BookJerseyTestBinder(true, FileServlet.class, MultiPartFeature.class)

The first boolean argument (true) is a flag to indicate whether or not, on executing the statement, servlet container starts immediately. If we would like to defer the startup, change that to false and manually start the container later by

jerseyTestBinder.start()

Internally JerseyTestBinder sets TestBinderFactory to bind those data and behaviors into the actual test

note

The JerseyTestBinder creates separate container for each test. Setup method is named setup() and teardown method cleanup() by Groovy Spock convention.

3. Running Tests

To send test request in order to test endpoints, use JerseyTestBinder.makeRequest method, which returns a native javax rs ws request object:

def "File meta data can be accessed through GraphQL GET endpoint"() {
when: "we get meta data via GraphQL GET"
String actual = jerseyTestBinder.makeRequest(
"/metadata/graphql",
[query: URLEncoder.encode("""{metaData(fileId:"$FILE_ID"){fileName\nfileType}}""", "UTF-8")]
).get(String.class)

then: "the response contains all requested metadata info without error"
new JsonSlurper().parseText(actual) == new JsonSlurper().parseText(expectedMultiFieldMetadataResponse())
}

4. Tearing Down Tests

The teardown shuts down test container as well as cleaning up all ApplicationStates we defined in step 1

def cleanup() {
// Release the test web container
jerseyTestBinder.tearDown()
}

Troubleshooting

Adding Custom Resources to ResourceConfig

java.lang.IllegalStateException: org.glassfish.jersey.server.model.ModelValidationException: Validation of the
application resource model has failed during application initialization.
[FATAL] No injection source found for a parameter of type public javax.ws.rs.core.Response

...

Caused by: org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has
failed during application initialization.

Athena uses ResourceConfig type for configuration. We need to register the MultiPartFeature. Instead of using Athena ResourceConfig, servlet test spec configures with the native Jersey ResourceConfig. The reason is so that we could bind certain resource classes that we only need in a test spec to enhance test efficiency.

Athena ResourceConfig registers MultiPartFeature by default, whereas Jersey ResourceConfig does not. We could register this resource as an extra resource class using

jerseyTestBinder = new JerseyTestBinder(true, applicationState, FileServlet.class, MultiPartFeature.class)

Note that along with the FileServlet resource that's going to be registered and tested, MultiPartFeature will also got registered by Jersey ResourceConfig.

Database

Database-related tests contain 2 parts

  1. Groovy Spock unit tests on

  2. Live DB tests on endpoints

Reference - Apache Commons DBCP2

For testing Book example application, we use Derby's in-memory database facility, which resides completely in main memory, not in the file system.

In addition, we use Apache DBCP 2 to provide DataSource pointing at the in-memory Derby instance.

Derby

Derby was meant to be used only in tests and, hence, must be imported in test scope only