Squit
is a Gradle
plugin for file-based, automated testing of JSON
, XML
, SOAP
and other apis.
It features high customizability and speed.
Add the plugin to your buildscript
:
plugins {
id "de.smartsquare.squit" version "5.1.0"
}
The minimum supported Gradle version is 6.8
.
Projects are structured in arbitrarily deep folders. The plugin expects the root to be in the src/squit
folder per
default.
A single test is represented by one leaf folder. That folder must contain:
- A
response
file (the file ending depends on the type of test).
Further it can contain:
- A
test.conf
file. - A
description.md
file. - A
request
file (the file ending depends on the type of test). db_$name_pre.sql
files.db_$name_post.sql
files.
The request
file contains whatever payload you want to send to your backend. The response
file contains the expected
response.
A test.conf
file is required at least once on the path of your test. That means that it is resolved recursively,
starting at the leaf, e.g. your test folder. The test.conf
can and must contain various properties, which are
discussed in the Configuration section. These properties are then merged if not existing while going
up the folder tree.
This allows for convenient definition of properties for multiple tests, with the ability to override properties in
special cases.
The description.md
file is an optional file containing additional descriptions for tests in
the Markdown format. If the tests are nested inside each other and there are
multiple description files on the path, they are merged together from top to bottom.
A simple example looks like this:
- src
--- squit
----- my_suite
------- test1 (folder)
--------- request.xml
--------- response.xml
--------- test.conf
--------- description.md
------- test2 (folder)
--------- request.xml
--------- response.xml
------- test.conf
This shows a valid project structure for Squit
. my_suite
contains all our tests (in this case only two: test1
and test2
).
You can have more directories beneath
my_suite
(e.g.another_suite
) and as aforementioned can also nest more deeply. At least one suite folder is required though, you can't have your tests directly in thesrc/squit
folder.
my_suite
also contains a test.conf
file, which could look like this:
endpoint = "http://localhost:1234/endpoint"
Squit
would then use http://localhost:1234/endpoint
as the endpoint to call when running all tests in my_suite
.
As the example shows, test1
also contains a test.conf
file. This one could be used to override the endpoint
property of the test.conf
file in the my_suite
folder.
It is not required to have a
test.conf
file there, often it is enough to have one for all your tests in the root folder.
The plugin is composed of various Gradle
tasks. For daily usage, only the squitTest
is relevant.
The following table lists all tasks and their purpose:
Task name | Description |
---|---|
squitPreProcess |
Pre processes the test sources in a configurable manner. |
squitRunRequests |
Runs the actual requests against your backend. |
squitPostProcess |
Post processes the responses in a configurable manner. |
squitTest |
Compares the expected and actual response and fails the build if differences were found. Also generates the report. |
To run all your tests, execute ./gradlew squitTest
.
You do NOT need to run the clean task every time you rerun the tests. Squit and Gradle will always run the tests, but only pre-process again if files have actually changed, saving execution time.
The plugin features a variety of configuration possibilities. As aforementioned, these are collected in test.conf
(
or local.conf
) files.
test.conf
files are in the HOCON format and support all of
its features. As of the current version, these are the supported properties:
Name | Description | Example |
---|---|---|
endpoint | The endpoint of your backend to call. | endpoint = "http://localhost:1234/api" |
mediaType | The media type of your content to send. | mediaType = "application/soap+xml" |
method | The method for the request to use. The default is POST and requires a request.xml. Methods like GET do not require one. | method = "GET" |
exclude | Excludes or un-excludes the test or test group. | exclude = true |
ignore | Ignores or un-ignores the test or test group. This means that the test is run, but does not show up in anything generated at the end of the build. | ignore = true |
databaseConfigurations | An array of database configurations to use for pre- and post scripts. See below for details. | databaseConfigurations = [ { / *content */ } ] |
preProcessors | An array of pre processor classes to use. | preProcessors = ["com.example.ExamplePreProcessor"] |
postProcessors | An array of post processor classes to use. | postProcessors = ["com.example.ExamplePostProcessor"] |
preProcessorScripts | An array of paths to groovy pre processor scripts to use. | preProcessorScripts = [./scripts/pre_processor.groovy] |
postProcessorScripts | An array of paths to groovy post processor scripts to use. | postProcessorScripts = [./scripts/post_processor.groovy] |
preRunners | An array of pre runner classes to use. | preRunners= ["com.example.ExamplePreRunner"] |
preTestTasks | An array of tasks to execute before the test. The order of the entries determines the order of execution. | preTestTasks = ["DATABASE_SCRIPTS", "PRE_RUNNERS", "PRE_RUNNER_SCRIPTS"] |
postRunners | An array of post runner classes to use. | postRunners = ["com.example.ExamplePostRunner"] |
preRunnerScripts | An array of paths to groovy pre runner scripts to use. | preRunnerScripts = [./scripts/pre_runner.groovy] |
postRunnerScripts | An array of paths to groovy post runner scripts to use. | postRunnerScripts = [./scripts/post_runner.groovy] |
postTestTasks | An array of tasks to execute after the test. The order of the entries determines the order of execution. | postTestTasks = ["DATABASE_SCRIPTS", "POST_RUNNERS", "POST_RUNNER_SCRIPTS"] |
headers | A map of headers to use for requests. | headers = { "some-header": "value" } |
title | An optional alternative title for the test. | title = "Something" |
expectedResponseCode | An optional expected HTTP response code. Default is the 200-range. | expectedResponseCode = 400 |
The parameter
endpoint
is required and the build will fail if it is missing for a test.
It may be useful to have a placeholder in a test.conf
file and fill it at runtime, for example when the port of an
endpoint is dynamic or when running in a CI environment.
The HOCON
config format which Squit
uses comes with support out of the box for it:
endpoint = "http://localhost:"${port}"/someEndpoint"
port
could then be replaced when invoking Squit
like this:
./gradlew squitTest -Psquit.port=1234
This mechanism can also be used to create global configuration properties, which are then used in configuration files deeper in the hierarchy.
Squit also allows for configuration to be stored in a local.conf
file. local.conf
files have a higher priority than
test.conf
file and thus override test.conf
files. This can be useful for overriding values of a versioned
test.conf
without having to check that change into a VCS for every collaborator on the project.
As part of your tests, you may want to modify your database into a specific state. Squit
allows you to do so with
ordinary sql
scripts which can be run before and after a test.
To do so, you have to add a database configuration to your test.conf
file(s) and specify the jdbc driver to use.
A simple example would look like this:
# test.conf
databaseConfigurations = [
{name = "mydb", jdbc = "jdbc:oracle:thin:@localhost:1521:xe", username = "someusername", password = "thepassword"}
// More are possible.
]
// build.gradle
squit {
jdbcDrivers = ['oracle.jdbc.driver.OracleDriver'] // You can add more if needed.
}
As you can see, the database properties follow a specific naming scheme. To be recognized, your database configurations
must start with db_
and you need all three shown variants (_jdbc
, _username
, _password
). The name in the middle
can be arbitrarily chosen and is later used to find the sql
scripts.
You would then have to add the jdbc driver to your classpath. This can be done like this (Assuming the jar
is in
the libs
directory of your project):
buildscript {
dependencies {
classpath files("libs/ojdbc6.jar")
}
}
The sql
files are added per test. They are required to be named after the configuration you added in the test.conf
file, ending with either _pre.sql
or _post.sql
. The example from before would be mydb_pre.sql
and mydb_post.sql
.
You can also add a sql
script in a higher level of your project structure to merge it into existing
scripts. _pre.sql
scripts are prepended and _post.sql
scrips are appended.
The last option is to have scripts which are only run once. For this, you name the script example_pre_once.sql
or example_post_once.sql
.
Squit
allows you to pre- and post-process the requests and actual responses. This may be required for incremental ids
you have no control over, dates or other things.
There are currently two ways to do so: Using Groovy
scripts or implementing a specific interface
.
Groovy
processing is the more easy, but less flexible option.
You add a .groovy
script somewhere in your project and supply Squit
with the path:
# test.conf
preProcessorScripts = [./some/path/pre_process.groovy]
As for the pre process step, the script gets passed request
, expectedResponse
and config
objects, which types
depend on the request type. See supported request types.
A simple script could look like this:
import java.time.LocalDate
request.selectNodes("//Date").each {
it.text = LocalDate.now().toString()
}
The passed objects for the post-processor are called
actualResponse
andexpectedResponse
. Note that theexpectedResponse
is only for reference here and changes to it are not reflected.
Implementing the Squit
interfaces is harder to set up, but much more flexible than the scripting approach.
To do so, you need to set up
a buildSrc project or an
external project, which is added to the classpath. In the following example, a buildSrc
project is set up:
Create the buildSrc
folder and set up a normal project in your preferred JVM language. After that, you add the Squit
library to your dependencies:
repositories {
gradlePluginPortal()
mavenCentral()
}
dependencies {
compile 'de.smartsquare:squit:5.1.0'
}
Then you can implement one of the interfaces
. An example for the SquitXmlPreProcessor
equivalent to the scripting
example could look like this:
import com.typesafe.config.Config;
import de.smartsquare.squit.SquitPreProcessor;
import org.dom4j.Document;
import java.time.LocalDate;
public class MyPreProcessor implements SquitXmlPreProcessor {
@Override
public void process(Document request, Document expectedResponse, Config config) {
request.selectNodes("//Date")
.forEach(it -> it.setText(LocalDate.now().toString()));
}
}
The other interfaces as of the current version are
SquitXmlPostProcessor
,SquitJsonPostProcessor
,SquitJsonPreProcessor
.
The last step is to add the class to your test.conf
, similar to the approach with groovy
scripts:
# test.conf
preProcessors = ["com.example.MyPreProcessor"]
Similar to Pre- and Post-processing, it is possible to specify implementations or scripts,
which can run arbitrary code before and after each request. The setup is analogous to pre- and post-processors. See
the Configuration section for the different options to enable them in your test.conf
.
Tags allow to run only a subset of your tests to save time and resources if needed.
You can specify tags in corresponding test.conf
files. An example could look like this:
tags = ["fast", "mysuite"]
All tests covered by this test.conf
file would then be tagged as fast
and mysuite
.
To run only tests with the tag fast
, you would invoke Squit
like so:
./gradlew squitTest -Ptags=fast
You can also specify more tags by separating with a ,
. Tags are then linked like an "and". If you
specify -Ptags=fast,mysuite
a test would need to have both tags to be included. If you want to have the semantics of
an "or", use -PtagsOr
.
Squit
also automatically tags your tests named on the folders they reside in. If you have a test in the folder test1
, it would have the tag test1
and could be run exclusively by invoking ./gradlew squitTest -Ptags=test1
.
As of the current version, Squit supports these request formats:
Media Type | File ending | Pre- and post-processor input |
---|---|---|
application/xml |
.xml |
Dom4J Documents |
application/json |
.json |
Gson JsonElements |
All others | .txt |
❌ |
Because Squit is a Gradle plugin, it supports all of Gradle's command line options and configuration options. Additionally, these options are evaluated:
Option | Description | Example |
---|---|---|
tags |
The tags tests must have to be included in a run. | ./gradlew squitTest -Ptags=fast |
tagsOr |
The tags tests must have at least one of to be included in a run. | ./gradlew squitTest -Ptags=fast,suite |
unignore |
Handle all tests as if the ignore property would not be set. | ./gradlew squitTest -Punignore |
unexclude |
Handle all tests as if the exclude property would not be set. |
./gradlew squitTest -Punexclude |
Here is a complete example of the Squit
dsl:
squit {
// The jdbc drivers to use. Must be on the classpath.
jdbcDrivers = ['oracle.jdbc.driver.OracleDriver']
// The path of your test sources. src/squit is the default.
sourceDir "src/squit"
// The path to save reports in. build/squit/reports is the default.
reportDir "build/squit/reports"
// The timeout in seconds for requests before squit fails. The default is 10.
timeout = 60
// If Squit should only print if a tests fails.
silent = false
// If the task should pass on test failures or not.
ignoreFailures = false
xml {
// If the xml diffing should be strict.
// If not, differences like namespace prefixes are not reported as differences.
strict = true
// If xml should be canonicalized for the html report.
// This means that both expected and actual response are transposed into a common format.
canonicalize = true
// If invalid namespace resources given in the xml should be resolved.
// If neither http:// or https:// are set as part of the namespace url, http:// will be set as a prefix,
// so that the canonicalization does not throw errors.
resolveInvalidNamespaces = true
}
json {
// If json should be canonicalized for the html report.
// This means that both expected and actual response are transposed into a common format.
canonicalize = true
}
}
All of the shown settings are optional.