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
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.
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 dataqueryFormatter
transforms a (file ID, metadata field list) pair to a native GraphQL querymutationFormatter
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.
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
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
-
Groovy Spock unit tests on
-
Live DB tests on endpoints
- In file servlet endpoint test and meta data servlet endpoint test, Flyway migration injects real data into a Derby in-meomroy SQL DB
- The Derby data is injected via a shared DBCP DataSource declared in application BinderFactory
- The application resource is set alive through JerseyTestBinder
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