This is a tool for generating Wiremock stubs from Spring @Controller & @RestController annotated classes
The WireMock generator has the following constraints and limitations:
- the request and response classes are shared between the producer and the consumers of the API
- currently, the generator supports only JSON based communication
Add the following dependencies to a Java project:
annotationProcessor 'io.github.lsd-consulting:spring-wiremock-stub-generator:x.x.x'
compileOnly 'io.github.lsd-consulting:spring-wiremock-stub-generator:x.x.x'
compileOnly 'org.wiremock:wiremock:3.5.2'
or these for a Kotlin project:
kapt 'io.github.lsd-consulting:spring-wiremock-stub-generator:x.x.x'
compileOnly 'io.github.lsd-consulting:spring-wiremock-stub-generator:x.x.x'
compileOnly 'org.wiremock:wiremock:3.5.2'
The above will set up the annotation processor which will analyse the source code and generate the Java WireMock stubs.
To compile the stubs add the following:
task compileStubs(type: JavaCompile) {
JavaCompile compileJava = project.getTasksByName("compileJava", true).toArray()[0]
classpath = compileJava.classpath
source = project.getLayout().getBuildDirectory().dir("generated-stub-sources")
def stubsClassesDir = file("${project.getBuildDir()}/generated-stub-classes")
destinationDir(stubsClassesDir)
compileJava.finalizedBy(compileStubs)
}
The result would be a class file(s) like this:
And to build a JAR file with the stubs:
task stubsJar(type: Jar) {
JavaCompile compileJavaStubs = project.getTasksByName("compileStubs", true).toArray()[0]
setDescription('Java Wiremock stubs JAR')
setGroup("Verification")
archiveBaseName.convention(project.provider(project::getName))
archiveClassifier.convention("wiremock-stubs")
from(compileJavaStubs.getDestinationDirectory())
dependsOn(compileJavaStubs)
compileJavaStubs.finalizedBy(stubsJar)
project.artifacts(artifactHandler -> artifactHandler.add("archives", stubsJar))
}
The JAR file can then be published as an artifact:
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifact stubsJar
artifactId "${rootProject.name}-${project.name}"
}
}
}
The last step is to import it as a dependency in another project and use it.
testImplementation('group:artifact:+:wiremock-stubs') {
exclude group: "*", module: "*"
}
NOTE: The exclusion is necessary if the published artifact imports transitively all the producer's dependencies.
For Java and Kotlin examples please check out the following project:
https://github.com/lsd-consulting/spring-wiremock-stub-generator-example
According to the accepted answer here and this Wiki page :
While there is no definitive standard, most web frameworks allow multiple values to be associated with a single field (e.g. field1=value1&field1=value2&field2=value3).
However, according to this thread, WireMock doesn't support duplicate query names.
Since Spring MVC does provide handling of duplicate query params out of the box, eg.
@GetMapping("/resourceWithParamSet")
fun resourceWithParamSet(@RequestParam paramSet: Set<String>) {
...
}
there is no easy way to set up a WireMock stub for the above, or similar, examples.
The library handles queries like this in a special way.
As soon as a multi-value query parameter is detected, the WireMock stub is switched from urlPathEqualTo
matcher, to the urlEqualTo
one.
The implication is that the ordering of the request parameters becomes significant.
Therefore, it is important to use ordered collections when sending multi-value parameters in tests to make the queries deterministic.
Optional request parameters is another feature of the HTTP protocol that is not yet supported by WireMock (see here).
To handle the following SpringMVC definition:
@GetMapping("/resourceWithOptionalBooleanRequestParam")
fun resourceWithOptionalBooleanRequestParam(@RequestParam(required = false) param: Boolean) {
...
}
the library introduces conditions into the generated stub.
If a parameter is optional and the value passed in is a null
, the generated stub will not add the query param matcher to the Wiremock stub.
This means that the following call to the generated stub:
underTest.resourceWithOptionalBooleanRequestParam(response, null)
will generate a WireMock matcher to match the following GET request:
/resourceWithOptionalBooleanRequestParam
But if a value is passed in that call, eg:
underTest.resourceWithOptionalBooleanRequestParam(response, "value")
then WireMock will expect the following request:
/resourceWithOptionalBooleanRequestParam?param=value
If the request parameter or path variable is a date, it needs to be annotated with @DateTimeFormat
, eg:
@GetMapping("/resource")
fun resource(@RequestParam @DateTimeFormat(iso = DATE_TIME) timestamp: ZonedDateTime)
For the generated stub object to be able to handle such values, it needs an AnnotationFormatterFactory
, eg. Jsr310DateTimeFormatAnnotationFormatterFactory
.
A special constructor needs to be used when creating an instance of the stub:
RestControllerStub(ObjectMapper(), Jsr310DateTimeFormatAnnotationFormatterFactory())
We welcome bug fixes and new features in the form of pull requests. If you'd like to contribute, please be mindful of the following guidelines:
- Start with raising an issue
- All changes should include suitable tests, whether to demonstrate the bug or exercise and document the new feature.
- Please make one change per pull request.
- Use conventional commits
- If the new feature is significantly large/complex/breaks existing behaviour, please first post a summary on the issue to generate a discussion. This will avoid significant amounts of coding time spent on changes that ultimately get rejected.
- Try to avoid reformats of files that change the indentation, spaces to tabs etc., as this makes reviewing diffs much more difficult.
Probably the best way to add functionality to this project is through tests.
Here is an example commit of adding support for Kotlin vararg
.
That commit doesn't show how the change actually came about, but here are the steps that were taken in the following order:
- Add a new resource to a controller in the integration tests
- Build the project so that the stubs are updated
- Add a failing integration test
- Make a change in the library that makes the test pass
In the majority of cases following the above steps should suffice.