> temp = new LinkedHashMap<>();
19 | temp.put("eq:", WireMock::equalTo);
20 | temp.put("eqIgnoreCase:", WireMock::equalToIgnoreCase);
21 | temp.put("eqToJson:", WireMock::equalToJson);
22 | temp.put("eqToXml:", WireMock::equalToXml);
23 | temp.put("matching:", WireMock::matching);
24 | temp.put("matchingXPath:", WireMock::matchingXPath);
25 | temp.put("matchingJsonPath:", WireMock::matchingJsonPath);
26 | temp.put("notMatching:", WireMock::notMatching);
27 | temp.put("containing:", WireMock::containing);
28 | builders = Map.copyOf(temp);
29 | }
30 |
31 | public static StringValuePattern parseFromPrefix(String pattern) {
32 | return builders.entrySet().stream()
33 | .filter(entry -> pattern.startsWith(entry.getKey()))
34 | .map(entry -> entry.getValue().apply(pattern.substring(entry.getKey().length())))
35 | .findFirst()
36 | .orElseGet(() -> WireMock.equalTo(pattern));
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/test/java/de/skuzzle/springboot/test/wiremock/metaannotations/WithSampleServiceMock.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock.metaannotations;
2 |
3 | import static java.lang.annotation.ElementType.TYPE;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.Target;
8 |
9 | import org.springframework.http.HttpStatus;
10 |
11 | import de.skuzzle.springboot.test.wiremock.WithWiremock;
12 | import de.skuzzle.springboot.test.wiremock.stubs.Auth;
13 | import de.skuzzle.springboot.test.wiremock.stubs.HttpStub;
14 | import de.skuzzle.springboot.test.wiremock.stubs.Request;
15 | import de.skuzzle.springboot.test.wiremock.stubs.Response;
16 | import de.skuzzle.springboot.test.wiremock.stubs.WrapAround;
17 |
18 | @Retention(RUNTIME)
19 | @Target(TYPE)
20 | @WithWiremock(injectHttpHostInto = "sample-service.url",
21 | withGlobalAuthentication = @Auth(
22 | basicAuthUsername = "user",
23 | basicAuthPassword = "password"))
24 | @HttpStub(onRequest = @Request(
25 | toUrl = "/info"),
26 | respond = @Response(
27 | withStatus = HttpStatus.OK,
28 | withStatusMessage = "Everything is Ok"))
29 | @HttpStub(onRequest = @Request(
30 | toUrl = "/submit/entity",
31 | withMethod = "PUT"),
32 | respond = {
33 | @Response(withStatus = HttpStatus.CREATED, withStatusMessage = "Entity created"),
34 | @Response(withStatus = HttpStatus.OK, withStatusMessage = "Entity already exists")
35 | }, onLastResponse = WrapAround.REPEAT)
36 | public @interface WithSampleServiceMock {
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/test/java/de/skuzzle/springboot/test/wiremock/HelloWorldTest.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 | import org.springframework.http.HttpStatus;
9 | import org.springframework.web.client.RestTemplate;
10 |
11 | import de.skuzzle.springboot.test.wiremock.stubs.HttpStub;
12 | import de.skuzzle.springboot.test.wiremock.stubs.Request;
13 | import de.skuzzle.springboot.test.wiremock.stubs.Response;
14 |
15 | @SpringBootTest
16 | @WithWiremock(injectHttpHostInto = "serviceUrl")
17 | public class HelloWorldTest {
18 |
19 | @Value("${serviceUrl}")
20 | private String serviceUrl;
21 |
22 | @Test
23 | @HttpStub(
24 | onRequest = @Request(withMethod = "POST"),
25 | respond = @Response(
26 | withStatus = HttpStatus.CREATED,
27 | withBody = "{\"value\": \"Hello World\"}",
28 | withContentType = "application/json"))
29 | void testCallWiremockWithRestTemplate() throws Exception {
30 | final var response = new RestTemplate().postForEntity(serviceUrl, null, HelloWorld.class);
31 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
32 | assertThat(response.getBody().getValue()).isEqualTo("Hello World");
33 | }
34 |
35 | static class HelloWorld {
36 | private String value;
37 |
38 | public String getValue() {
39 | return this.value;
40 | }
41 |
42 | public void setValue(String value) {
43 | this.value = value;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent {
3 | docker {
4 | image 'maven:3.8-jdk-11'
5 | args '-v /home/jenkins/.m2:/var/maven/.m2 -v /home/jenkins/.gnupg:/.gnupg -e MAVEN_CONFIG=/var/maven/.m2 -e MAVEN_OPTS=-Duser.home=/var/maven'
6 | }
7 | }
8 | environment {
9 | COVERALLS_REPO_TOKEN = credentials('coveralls_repo_token_spring_boot_wiremock')
10 | GPG_SECRET = credentials('gpg_password')
11 | }
12 | stages {
13 | stage ('Test Spring-Boot Compatibility') {
14 | steps {
15 | script {
16 | def versionsAsString = sh(script: 'mvn org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate -Dexpression=compatible-spring-boot-versions -q -DforceStdout', returnStdout:true).trim()
17 | def versionsArray = versionsAsString.split(',')
18 | versionsArray.each {
19 | stage("Verify against Spring-Boot ${it}") {
20 | sh "mvn -B clean verify -Dversion.spring-boot=${it.trim()}"
21 | }
22 | }
23 | }
24 | }
25 | }
26 | stage('Build Final') {
27 | steps {
28 | sh 'mvn -B clean verify'
29 | }
30 | }
31 | stage('Coverage') {
32 | steps {
33 | sh 'mvn -B jacoco:report jacoco:report-integration coveralls:report -DrepoToken=$COVERALLS_REPO_TOKEN'
34 | }
35 | }
36 | stage('javadoc') {
37 | steps {
38 | sh 'mvn -B javadoc:javadoc'
39 | }
40 | }
41 | stage('Deploy SNAPSHOT') {
42 | when {
43 | branch 'dev'
44 | }
45 | steps {
46 | sh 'mvn -B -Prelease -DskipTests -Dgpg.passphrase=${GPG_SECRET} deploy'
47 | }
48 | }
49 | }
50 | post {
51 | always {
52 | archiveArtifacts(artifacts: '*.md')
53 | junit (testResults: 'target/surefire-reports/*.xml', allowEmptyResults: true)
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/main/java/de/skuzzle/springboot/test/wiremock/stubs/Response.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock.stubs;
2 |
3 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
4 |
5 | import java.lang.annotation.Retention;
6 |
7 | import org.apiguardian.api.API;
8 | import org.apiguardian.api.API.Status;
9 | import org.springframework.http.HttpStatus;
10 |
11 | /**
12 | * Defines the contents of the mock response that will be sent when the request of a
13 | * {@link HttpStub stub} was matched. When no attributes are defined, the response will be
14 | * empty with status 200 OK.
15 | *
16 | * @author Simon Taddiken
17 | */
18 | @API(status = Status.EXPERIMENTAL)
19 | @Retention(RUNTIME)
20 | public @interface Response {
21 |
22 | /** The HTTP status of the response. Defaults to {@link HttpStatus#OK}. */
23 | HttpStatus withStatus() default HttpStatus.OK;
24 |
25 | /** The HTTP status line. */
26 | String withStatusMessage() default "";
27 |
28 | /**
29 | * The body of the response as json string. Will also set the Content-Type to
30 | * application/json. The Content-Type can still be overridden by
31 | * {@link #withContentType()} or {@link #withHeaders()}.
32 | *
33 | * Mutual exclusive to {@link #withBody()}, {@link #withBodyFile()} and
34 | * {@link #withBodyBase64()}. Defaults to 'no body'.
35 | *
36 | * @since 0.0.17
37 | */
38 | String withJsonBody() default "";
39 |
40 | /**
41 | * The body of the response. Mutual exclusive to {@link #withBodyBase64()},
42 | * {@link #withJsonBody()} and {@link #withBodyFile()}. Defaults to 'no body'.
43 | */
44 | String withBody() default "";
45 |
46 | /**
47 | * The body of the response. Mutual exclusive to {@link #withBody()},
48 | * {@link #withJsonBody()} and {@link #withBodyFile()}. Defaults to 'no body'.
49 | */
50 | String withBodyBase64() default "";
51 |
52 | /**
53 | * The body of the response. Mutual exclusive to {@link #withBody()},
54 | * {@link #withJsonBody()} and {@link #withBodyBase64()}. Defaults to 'no body'.
55 | *
56 | * By default, files must be contained in a folder on the classpath called
57 | * {@code __files}.
58 | */
59 | String withBodyFile() default "";
60 |
61 | /**
62 | * Content-Type for the response. If configured, this value takes precedence if
63 | * {@code "Content-Type"} is also configured using {@link #withHeaders()}.
64 | */
65 | String withContentType() default "";
66 |
67 | /**
68 | * Headers that will be added to the response. Specify pairs like
69 | * {@code "Content-Type=application/json"}
70 | */
71 | String[] withHeaders() default {};
72 | }
73 |
--------------------------------------------------------------------------------
/CHANGELOG_LEGACY.md:
--------------------------------------------------------------------------------
1 |
2 | This changelog is no longer maintained. Follow the release notes at the GitHub releases for latest changes
3 |
4 | ## Changelog
5 |
6 | ### Version 0.0.14
7 | * [Dependency] Update to WireMock 2.27.2
8 |
9 | ### Version 0.0.13
10 | * Improve documentation
11 | * [Change] Move stubbing annotations into their own package: `de.skuzzle.wiremock.test.stubs` (**breaking**)
12 | * [Change] Deprecated `HttpStub.wrapAround` and introduced `HttpStub.onLastResponse` with new enum `WrapAround`
13 | * [Add] New properties that will always be injected: `wiremock.server.http(s)Host`, `wiremock.server.http(s)Port`
14 | * [Add] `WrapAround.REPEAT` which will repeat the last response on every subsequent request
15 | * [Add] Allow to globally define required authentication via `WithWiremock.withGlobalAuthentication`
16 |
17 |
18 | ### Version 0.0.12
19 | * Just some improvements to the build/release process
20 |
21 | ### Version 0.0.11
22 | * Just some improvements to the build/release process
23 |
24 | ### Version 0.0.10
25 | * [Fix] Readme
26 | * [Change] Use latest WireMock version (`2.27.1`)
27 |
28 | ### Version 0.0.9
29 | * [Add] Possibility to set a stub's priority
30 | * [Add] Allow to define annotation stubs on inherited super classes and interfaces of the test class
31 | * [Add] Allow to define annotation stubs using meta-annotated custom annotations
32 | * [Fix] Possibility to place multiple stubs on the test class (missing `target = { ..., ElementType.TYPE }` on `HttpStubs`)
33 |
34 | ### Version 0.0.8
35 | * Allow to configure consecutive responses for the same request
36 |
37 | ### Version 0.0.7
38 | * Compatibility to older Spring-Boot versions
39 | * Remove note about Junit 5 being required. This library actually isn't tied to a specific testing framework
40 |
41 | ### Version 0.0.6
42 | * Improve JavaDoc
43 | * Add automatic module name to jar manifest
44 |
45 | ### Version 0.0.5
46 | * Improve JavaDoc
47 | * Improve configuration consistency checks
48 | * Allow `@HttpStub` on test class itself (instead of only on test method)
49 | * Allow to set _status message_ on mock response
50 | * Allow to configure WireMock _scenarios_ for stateful request matching using annotations
51 |
52 | ### Version 0.0.4
53 | * Skipped by accident 🤡
54 |
55 | ### Version 0.0.3
56 | * Renamed `SimpleStub` to `HttpStub` and split into multiple annotations
57 | * `HttpStatus` enum is now used for defining the stubbed response status
58 | * Match _any_ HTTP method by default (instead of _GET_)
59 | * Allow to define different matchers for params, cookies, headers and body using prefixes like `eq:` or `containing:`
60 |
61 | ### Version 0.0.2
62 | * Support multiple `@SimpleStub` instances per test method
63 | * Allow to stub authentication and response headers via `@SimpleStub`
64 | * Fix bug with unresolvable test keystore locations
65 |
66 | ### Version 0.0.1
67 | * Initial prototype
68 |
--------------------------------------------------------------------------------
/JenkinsfileRelease:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent {
3 | docker {
4 | image 'maven:3.8-jdk-11'
5 | args '-v /home/jenkins/.m2:/var/maven/.m2 -v /home/jenkins/.gnupg:/.gnupg -e MAVEN_CONFIG=/var/maven/.m2 -e MAVEN_OPTS=-Duser.home=/var/maven'
6 | }
7 | }
8 | environment {
9 | GPG_SECRET = credentials('gpg_password')
10 | GITHUB = credentials('Github-Username-Pw')
11 | GITHUB_RELEASE_TOKEN = credentials('github_registry_release')
12 | GIT_ASKPASS='./.git-askpass'
13 | }
14 | stages {
15 | stage ('Ensure dev branch') {
16 | when {
17 | expression {
18 | return env.BRANCH_NAME != 'dev';
19 | }
20 | }
21 | steps {
22 | error("Releasing is only possible from dev branch")
23 | }
24 | }
25 | stage ('Set Git Information') {
26 | steps {
27 | sh 'echo \'echo \$GITHUB_PSW\' > ./.git-askpass'
28 | sh 'chmod +x ./.git-askpass'
29 | sh 'git config url."https://api@github.com/".insteadOf "https://github.com/"'
30 | sh 'git config url."https://ssh@github.com/".insteadOf "ssh://git@github.com/"'
31 | sh 'git config url."https://git@github.com/".insteadOf "git@github.com:"'
32 | sh 'git config user.email "build@taddiken.online"'
33 | sh 'git config user.name "Jenkins"'
34 | }
35 | }
36 | stage('Create release branch') {
37 | steps {
38 | sh 'mvn -B -Prelease gitflow:release-start'
39 | }
40 | }
41 |
42 | stage ('Test Spring-Boot Compatibility') {
43 | steps {
44 | script {
45 | def versionsAsString = sh(script: 'mvn org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate -Dexpression=compatible-spring-boot-versions -q -DforceStdout', returnStdout:true).trim()
46 | def versionsArray = versionsAsString.split(',')
47 | versionsArray.each {
48 | stage("Verify against Spring-Boot ${it}") {
49 | sh "mvn -B -Prelease -Dgpg.passphrase=${GPG_SECRET} clean verify -Dversion.spring-boot=${it.trim()}"
50 | }
51 | }
52 | }
53 | }
54 | }
55 | stage('Verify Release') {
56 | steps {
57 | sh 'mvn -B -Prelease -Dgpg.passphrase=${GPG_SECRET} verify'
58 | }
59 | }
60 | stage('Update readme') {
61 | steps {
62 | sh 'git add README.md RELEASE_NOTES.md'
63 | sh 'git commit -m "Update README and RELEASE_NOTES"'
64 | }
65 | }
66 | stage('Perform release') {
67 | steps {
68 | sh "mvn -B gitflow:release-finish -DargLine=\"-Prelease -B -Dgpg.passphrase=${GPG_SECRET} -DskipTests\""
69 | }
70 | }
71 | stage('Create GitHub release') {
72 | steps {
73 | sh 'git checkout main'
74 | sh "mvn -B github-release:github-release -Dgithub.release-token=${GITHUB_RELEASE_TOKEN}"
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/examples/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | de.skuzzle.springboot.test
6 | spring-boot-wiremock-parent
7 | 0.0.18
8 |
9 |
10 | spring-boot-wiremock-examples
11 |
12 |
13 |
14 | de.skuzzle.springboot.test
15 | spring-boot-wiremock
16 | test
17 |
18 |
19 |
20 |
21 | org.springframework
22 | spring-context
23 |
24 |
25 | org.springframework
26 | spring-beans
27 |
28 |
29 | org.springframework
30 | spring-web
31 | provided
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter
37 | compile
38 |
39 |
40 | org.springframework.boot
41 | spring-boot
42 | compile
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-autoconfigure
47 |
48 |
49 | org.springframework.boot
50 | spring-boot-test
51 |
52 |
53 | org.junit.vintage
54 | junit-vintage-engine
55 |
56 |
57 |
58 |
59 | org.assertj
60 | assertj-core
61 | compile
62 |
63 |
64 | org.junit.jupiter
65 | junit-jupiter-api
66 | compile
67 |
68 |
69 | org.junit.jupiter
70 | junit-jupiter-params
71 | test
72 |
73 |
74 |
75 |
76 |
77 |
78 | maven-dependency-plugin
79 |
80 |
81 | org.springframework.boot:spring-boot-starter
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/main/java/de/skuzzle/springboot/test/wiremock/stubs/HttpStub.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock.stubs;
2 |
3 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Repeatable;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | import org.apiguardian.api.API;
11 | import org.apiguardian.api.API.Status;
12 |
13 | import com.github.tomakehurst.wiremock.WireMockServer;
14 |
15 | /**
16 | * Allows to configure a simple, single stub for a test case by annotating the test method
17 | * or the test class. All attributes are optional. An empty {@code @HttpStub} on a test
18 | * method will set up a stub that returns a simple {@code 200 OK} response for every
19 | * incoming request.
20 | *
21 | * In order to refine the matching and the produced response, see {@link #onRequest()} and
22 | * {@link #respond()}. Here is a basic example:
23 | *
24 | *
25 | * @Test
26 | * @HttpStub(
27 | * onRequest = @Request(
28 | * toUrl("/createItem"),
29 | * withMethod("POST"),
30 | * withCookie("jsessionid=matching:[a-z0-9]+")
31 | * authenticatedBy = @Auth(
32 | * basicAuthUsername = "username",
33 | * basicAuthPassword = "password"))
34 | * respond = @Response(
35 | * withStatus(HttpStatus.CREATED),
36 | * withHeader("location="/newItem"),
37 | * withBody("{ \"status\": \"SUCCESS\" }")))
38 | * void testCreateItem() {
39 | *
40 | * }
41 | *
42 | *
43 | * It is possible to configure multiple {@link #respond() responses}. If more than one
44 | * response is specified, the responses will be returned consecutively for each matched
45 | * request.
46 | *
47 | * The annotation can be put in various places:
48 | *
49 | * - You can place the annotation on a single test method.
50 | * - You can place the annotation on the test class itself to define global stubs.
51 | * - You can place the annotation on a super class or any implemented interface of a
52 | * test class for easy reuse of the stub.
53 | * - You can place the annotation as meta-annotation on a custom annotation type for
54 | * easy reuse of the stub.
55 | * - The annotation is repeatable. Wherever you put a single instance, you can also put
56 | * multiple instances to define multiple stubs.
57 | *
58 | * Note that all stubs are reset after each test.
59 | *
60 | * If you need more sophisticated stubbing, you can always just autowire the
61 | * {@link WireMockServer} into your test class and use
62 | * {@link WireMockServer#stubFor(com.github.tomakehurst.wiremock.client.MappingBuilder)}.
63 | * The same approach should be used to use verifications which are not supported via
64 | * annotations.
65 | *
66 | * @author Simon Taddiken
67 | * @see Request
68 | * @see Response
69 | */
70 | @API(status = Status.EXPERIMENTAL)
71 | @Repeatable(HttpStubs.class)
72 | @Retention(RUNTIME)
73 | @Target({ ElementType.METHOD, ElementType.TYPE })
74 | public @interface HttpStub {
75 |
76 | /**
77 | * The request that must be matched in order to produce a mock {@link #respond()
78 | * response}. By default, every request will be matched.
79 | */
80 | Request onRequest() default @Request;
81 |
82 | /**
83 | * The mock responses that will consecutively be returned by the server if an incoming
84 | * request matched what has been configured in {@link #onRequest()}. By default,
85 | * returns an empty 200 OK message with no further details.
86 | */
87 | Response[] respond() default { @Response };
88 |
89 | /**
90 | * Defines the response behavior of the mock if multiple responses are defined. By
91 | * default, when the last response has been returned, the mock will answer with a 403
92 | * status code (see {@link WrapAround#RETURN_ERROR}).
93 | */
94 | WrapAround onLastResponse() default WrapAround.RETURN_ERROR;
95 | }
96 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/test/java/de/skuzzle/springboot/test/wiremock/NestedTestTest.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.boot.context.properties.ConfigurationProperties;
9 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 |
12 | public class NestedTestTest {
13 |
14 | @SpringBootTest
15 | @WithWiremock(injectHttpHostInto = { "httpHost1", "httpHost2" })
16 | static class TestInjectMultipleHttpHosts {
17 |
18 | @Value("${httpHost1}")
19 | private String host1;
20 | @Value("${httpHost2}")
21 | private String host2;
22 |
23 | @Test
24 | void testInjectHost1() throws Exception {
25 | assertThat(host1).startsWith("http:");
26 | }
27 |
28 | @Test
29 | void testInjectHost2() throws Exception {
30 | assertThat(host2).startsWith("http:");
31 | }
32 | }
33 |
34 | @SpringBootTest
35 | @WithWiremock(injectHttpsHostInto = { "httpsHost1", "httpsHost2" }, randomHttpsPort = true)
36 | static class TestInjectMultipleHttpsHosts {
37 |
38 | @Value("${httpsHost1}")
39 | private String host1;
40 | @Value("${httpsHost2}")
41 | private String host2;
42 |
43 | @Test
44 | void testInjectHost1() throws Exception {
45 | assertThat(host1).startsWith("https:");
46 | }
47 |
48 | @Test
49 | void testInjectHost2() throws Exception {
50 | assertThat(host2).startsWith("https:");
51 | }
52 | }
53 |
54 | @SpringBootTest
55 | @WithWiremock(fixedHttpPort = 13337)
56 | static class TestFixedHttpPort {
57 | @Value("${wiremock.server.httpHost}")
58 | private String host;
59 |
60 | @Test
61 | void testInjectHost1() throws Exception {
62 | assertThat(host).endsWith(":13337");
63 | }
64 | }
65 |
66 | @SpringBootTest
67 | @WithWiremock(fixedHttpPort = 13338, randomHttpPort = true)
68 | static class TestFixedHttpPortTakesPrecedenceOverRandomHttpPort {
69 | @Value("${wiremock.server.httpHost}")
70 | private String host;
71 |
72 | @Test
73 | void testInjectHost() throws Exception {
74 | assertThat(host).endsWith(":13338");
75 | }
76 | }
77 |
78 | @SpringBootTest
79 | @WithWiremock(fixedHttpsPort = 13339)
80 | static class TestFixedHttpsPort {
81 | @Value("${wiremock.server.httpsHost}")
82 | private String host;
83 |
84 | @Test
85 | void testInjectHost() throws Exception {
86 | assertThat(host).endsWith(":13339");
87 | }
88 | }
89 |
90 | @SpringBootTest
91 | @WithWiremock(randomHttpsPort = true)
92 | static class TestRandomHttpsHost {
93 | @Value("${wiremock.server.httpsHost}")
94 | private String host;
95 |
96 | @Test
97 | void testContextStarts() throws Exception {
98 | }
99 | }
100 |
101 | @SpringBootTest
102 | @EnableConfigurationProperties(TestConfigurationProperties.class)
103 | @WithWiremock(injectHttpHostInto = "url.in.propertiesfile")
104 | static class InjectIntoConfigurationProperties {
105 | @Autowired
106 | private TestConfigurationProperties properties;
107 | @Value("${url.in.propertiesfile}")
108 | private String host;
109 |
110 | @Test
111 | void testWiremockLocationTakesPrecedenceOverStaticConfiguration() throws Exception {
112 | assertThat(properties.getPropertiesfile()).isEqualTo(host);
113 | }
114 | }
115 |
116 | @ConfigurationProperties("url.in")
117 | static class TestConfigurationProperties {
118 | private String propertiesfile;
119 |
120 | public String getPropertiesfile() {
121 | return this.propertiesfile;
122 | }
123 |
124 | public void setPropertiesfile(String propertiesfile) {
125 | this.propertiesfile = propertiesfile;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/test/java/de/skuzzle/springboot/test/wiremock/client/TestClients.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock.client;
2 |
3 | import java.security.KeyStore;
4 | import java.security.KeyStoreException;
5 | import java.security.NoSuchAlgorithmException;
6 | import java.security.UnrecoverableKeyException;
7 | import java.util.function.Function;
8 |
9 | import org.apache.http.impl.client.CloseableHttpClient;
10 | import org.apache.http.impl.client.HttpClientBuilder;
11 | import org.apache.http.ssl.SSLContextBuilder;
12 | import org.springframework.boot.web.client.RestTemplateBuilder;
13 | import org.springframework.http.client.ClientHttpRequestFactory;
14 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
15 | import org.springframework.web.client.RestTemplate;
16 |
17 | public final class TestClients {
18 |
19 | public static ClientBuilder restTemplate() {
20 | return new RestTemplateClientBuilder(new RestTemplateBuilder());
21 | }
22 |
23 | public static interface ClientBuilder {
24 | ClientBuilder customize(Function super T, T> builder);
25 |
26 | ClientBuilder withBasicAuth(String username, String password);
27 |
28 | ClientBuilder withBaseUrl(String baseUrl);
29 |
30 | ClientBuilder withClientAuth(KeyStore keystore, char[] keyPassword);
31 |
32 | ClientBuilder trusting(KeyStore truststore);
33 |
34 | C build();
35 | }
36 |
37 | public static final class RestTemplateClientBuilder implements ClientBuilder {
38 |
39 | private RestTemplateBuilder builder;
40 | private final SSLContextBuilder sslContextBuilder;
41 |
42 | public RestTemplateClientBuilder(RestTemplateBuilder builder) {
43 | this.builder = builder;
44 | this.sslContextBuilder = SSLContextBuilder.create();
45 | }
46 |
47 | @Override
48 | public ClientBuilder customize(
49 | Function super RestTemplateBuilder, RestTemplateBuilder> builder) {
50 | this.builder = builder.apply(this.builder);
51 | return this;
52 | }
53 |
54 | @Override
55 | public ClientBuilder withBasicAuth(String username, String password) {
56 | builder = builder.basicAuthentication(username, password);
57 | return this;
58 | }
59 |
60 | @Override
61 | public ClientBuilder withBaseUrl(String baseUrl) {
62 | builder = builder.rootUri(baseUrl);
63 | return this;
64 | }
65 |
66 | @Override
67 | public ClientBuilder withClientAuth(KeyStore keystore, char[] keyPassword) {
68 | try {
69 | sslContextBuilder.loadKeyMaterial(keystore, keyPassword);
70 | } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
71 | throw new IllegalArgumentException("Error configuring keystore", e);
72 | }
73 | return this;
74 | }
75 |
76 | @Override
77 | public ClientBuilder trusting(KeyStore truststore) {
78 | try {
79 | sslContextBuilder.loadTrustMaterial(truststore, null);
80 | } catch (NoSuchAlgorithmException | KeyStoreException e) {
81 | throw new IllegalArgumentException("Error configuring truststore", e);
82 | }
83 |
84 | return this;
85 | }
86 |
87 | @Override
88 | public RestTemplate build() {
89 | try {
90 | final CloseableHttpClient httpClient = HttpClientBuilder.create()
91 | .setSSLContext(sslContextBuilder.build())
92 | .build();
93 |
94 | final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
95 | return builder
96 | .requestFactory(() -> requestFactory)
97 | .build();
98 | } catch (final Exception e) {
99 | throw new IllegalArgumentException("Error configuring test client", e);
100 | }
101 | }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/main/java/de/skuzzle/springboot/test/wiremock/TestKeystores.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.net.URL;
6 | import java.security.KeyStore;
7 | import java.security.KeyStoreException;
8 | import java.security.NoSuchAlgorithmException;
9 | import java.security.cert.CertificateException;
10 |
11 | import org.apiguardian.api.API;
12 | import org.apiguardian.api.API.Status;
13 |
14 | import com.google.common.io.Resources;
15 |
16 | /**
17 | * Holds the locations for the default key- and truststores that are used for mock SSL
18 | * connections. These certificates can be used if no custom key- or truststore have been
19 | * configured in your test.
20 | *
21 | * Never use any of these in production.
22 | *
23 | * @author Simon Taddiken
24 | */
25 | @API(status = Status.EXPERIMENTAL)
26 | public final class TestKeystores {
27 |
28 | private TestKeystores() {
29 | }
30 |
31 | /**
32 | * A keystore containing a client certificate that is considered valid by the mock
33 | * server.
34 | */
35 | public static final KeystoreLocation TEST_CLIENT_CERTIFICATE = new KeystoreLocation(
36 | "certs/client_keystore.pkcs12",
37 | "password",
38 | "PKCS12");
39 | /**
40 | * A truststore for trusting the client certificate contained in
41 | * {@link #TEST_CLIENT_CERTIFICATE}.
42 | */
43 | public static final KeystoreLocation TEST_CLIENT_CERTIFICATE_TRUST = new KeystoreLocation(
44 | "certs/server_truststore.jks",
45 | "password",
46 | "JKS");
47 | /**
48 | * A keystore containing a self signed server certificate which is used by the mock.
49 | */
50 | public static final KeystoreLocation TEST_SERVER_CERTIFICATE = new KeystoreLocation(
51 | "certs/server_keystore.jks",
52 | "password",
53 | "JKS");
54 | /**
55 | * A truststore for trusting the self signed server certificate contained in
56 | * {@link #TEST_SERVER_CERTIFICATE}
57 | */
58 | public static final KeystoreLocation TEST_SERVER_CERTIFICATE_TRUST = new KeystoreLocation(
59 | "certs/client_truststore.jks",
60 | "password",
61 | "JKS");
62 |
63 | /**
64 | * Information about a test keystore.
65 | *
66 | * @author Simon Taddiken
67 | */
68 | public static final class KeystoreLocation {
69 | private final String classpathLocation;
70 | private final String password;
71 | private final String type;
72 |
73 | private KeystoreLocation(String classpathLocation, String password, String type) {
74 | this.classpathLocation = classpathLocation;
75 | this.password = password;
76 | this.type = type;
77 | }
78 |
79 | /**
80 | * Location resolved from classpath.
81 | *
82 | * @return The location as URL.
83 | */
84 | public URL toURL() {
85 | return Resources.getResource(getClasspathLocation());
86 | }
87 |
88 | /**
89 | * Location that can be resolved using {@link ClassLoader#getResource(String)}.
90 | *
91 | * @return The classpath location.
92 | */
93 | public String getClasspathLocation() {
94 | return classpathLocation;
95 | }
96 |
97 | public String getLocation() {
98 | return toURL().toString();
99 | }
100 |
101 | public String getPassword() {
102 | return this.password;
103 | }
104 |
105 | public String getType() {
106 | return this.type;
107 | }
108 |
109 | /**
110 | * Materializes the keystore from its location.
111 | *
112 | * @return The keystore.
113 | */
114 | public KeyStore getKeystore() {
115 | try (InputStream in = toURL().openStream()) {
116 | final KeyStore keyStore = KeyStore.getInstance(getType());
117 | keyStore.load(in, getPassword().toCharArray());
118 | return keyStore;
119 | } catch (final KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
120 | throw new IllegalStateException(
121 | "Could not read keystore from classpath location: " + classpathLocation);
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | de.skuzzle
7 | skuzzle-parent
8 | 3.0.1
9 |
10 |
11 | de.skuzzle.springboot.test
12 | spring-boot-wiremock-parent
13 | 0.0.18
14 | https://github.com/skuzzle/spring-boot-wiremock
15 | pom
16 |
17 |
18 | false
19 |
20 |
21 | 2.2.13.RELEASE
22 | 2.27.2
23 | 30.1.1-jre
24 | 1.1.2
25 | 3.7.1
26 |
27 | 2.3.12.RELEASE, 2.4.11, 2.5.5
28 |
29 | spring-boot-wiremock
30 | spring-boot-wiremock
31 |
32 |
33 |
34 |
35 | spring-boot-wiremock
36 | examples
37 |
38 |
39 |
40 |
41 |
42 | de.skuzzle.springboot.test
43 | spring-boot-wiremock
44 | ${project.version}
45 |
46 |
47 |
48 | org.springframework.boot
49 | spring-boot-dependencies
50 | ${version.spring-boot}
51 | pom
52 | import
53 |
54 |
55 | com.github.tomakehurst
56 | wiremock-jre8
57 | ${version.wiremock}
58 |
59 |
60 | com.google.guava
61 | guava
62 | ${version.guava}
63 |
64 |
65 | org.apiguardian
66 | apiguardian-api
67 | ${version.api-guardian}
68 |
69 |
70 | nl.jqno.equalsverifier
71 | equalsverifier
72 | ${version.equalsverifier}
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | com.amashchenko.maven.plugin
81 | gitflow-maven-plugin
82 | false
83 |
84 | deploy
85 | true
86 | true
87 | true
88 |
89 | main
90 | dev
91 |
92 |
93 |
94 |
95 | com.ragedunicorn.tools.maven
96 | github-release-maven-plugin
97 | 1.0.2
98 | false
99 |
100 | skuzzle
101 | ${github.name}
102 | ${github.release-token}
103 | v${project.version}
104 | ${project.version}
105 | main
106 | RELEASE_NOTES.md
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/main/java/de/skuzzle/springboot/test/wiremock/WiremockContextCustomizer.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock;
2 |
3 | import java.lang.reflect.AnnotatedElement;
4 | import java.util.Map;
5 | import java.util.Objects;
6 | import java.util.stream.Stream;
7 |
8 | import org.springframework.boot.test.util.TestPropertyValues;
9 | import org.springframework.context.ConfigurableApplicationContext;
10 | import org.springframework.context.event.ContextClosedEvent;
11 | import org.springframework.core.annotation.MergedAnnotation;
12 | import org.springframework.core.annotation.MergedAnnotations;
13 | import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
14 | import org.springframework.test.context.ContextCustomizer;
15 | import org.springframework.test.context.MergedContextConfiguration;
16 | import org.springframework.test.context.TestContext;
17 | import org.springframework.test.context.event.AfterTestExecutionEvent;
18 | import org.springframework.test.context.event.BeforeTestExecutionEvent;
19 |
20 | import com.github.tomakehurst.wiremock.WireMockServer;
21 | import com.google.common.base.Preconditions;
22 |
23 | import de.skuzzle.springboot.test.wiremock.stubs.HttpStub;
24 |
25 | /**
26 | * Starts and manages the lifecycle of the WireMock server and injects its hosts into the
27 | * properties defined in {@link WithWiremock#injectHttpHostInto()} and
28 | * {@link WithWiremock#injectHttpsHostInto()}.
29 | *
30 | * @author Simon Taddiken
31 | */
32 | final class WiremockContextCustomizer implements ContextCustomizer {
33 |
34 | private final WiremockAnnotationConfiguration wiremockProps;
35 |
36 | public WiremockContextCustomizer(WiremockAnnotationConfiguration wiremockProps) {
37 | Preconditions.checkArgument(wiremockProps != null, "props must not be null");
38 | this.wiremockProps = wiremockProps;
39 | }
40 |
41 | @Override
42 | public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
43 | final WireMockServer server = startServer();
44 | injectHost(context, server);
45 | addLifecycleEvents(context, server);
46 | }
47 |
48 | private WireMockServer startServer() {
49 | final WireMockServer server = wiremockProps.createWireMockServer();
50 | server.start();
51 | return server;
52 | }
53 |
54 | private void injectHost(ConfigurableApplicationContext context, WireMockServer server) {
55 | final Map propertiesToInject = wiremockProps.determineInjectionPropertiesFrom(server);
56 | TestPropertyValues
57 | .of(toStringProps(propertiesToInject))
58 | .applyTo(context);
59 | }
60 |
61 | private void addLifecycleEvents(ConfigurableApplicationContext applicationContext,
62 | WireMockServer wiremockServer) {
63 | applicationContext.addApplicationListener(applicationEvent -> {
64 | if (applicationEvent instanceof BeforeTestExecutionEvent) {
65 | final BeforeTestExecutionEvent e = (BeforeTestExecutionEvent) applicationEvent;
66 |
67 | final WithWiremock withWiremock = this.wiremockProps.annotation();
68 |
69 | final TestContext testContext = e.getTestContext();
70 | Stream.concat(
71 | determineStubs(testContext.getTestClass()),
72 | determineStubs(testContext.getTestMethod()))
73 | .forEach(stub -> StubTranslator.configureStubOn(wiremockServer, withWiremock, stub));
74 |
75 | }
76 | if (applicationEvent instanceof AfterTestExecutionEvent) {
77 | wiremockServer.resetAll();
78 | }
79 | if (applicationEvent instanceof ContextClosedEvent) {
80 | wiremockServer.stop();
81 | }
82 | });
83 | }
84 |
85 | private Stream determineStubs(AnnotatedElement e) {
86 | return MergedAnnotations.from(e, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
87 | .stream(HttpStub.class)
88 | .map(MergedAnnotation::synthesize);
89 |
90 | }
91 |
92 | @Deprecated
93 | private Stream toStringProps(Map props) {
94 | // Only for compatibility to older Spring-Boot versions that do not support
95 | // TestPropertyValues.of(Map)
96 | // This method can be removed when the base-line spring-boot version is 2.4.x
97 | return props.entrySet().stream()
98 | .map(entry -> entry.getKey() + "=" + entry.getValue());
99 | }
100 |
101 | @Override
102 | public int hashCode() {
103 | return Objects.hash(wiremockProps);
104 | }
105 |
106 | @Override
107 | public boolean equals(Object obj) {
108 | return obj == this || obj instanceof WiremockContextCustomizer
109 | && Objects.equals(wiremockProps, ((WiremockContextCustomizer) obj).wiremockProps);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/main/java/de/skuzzle/springboot/test/wiremock/WithWiremock.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock;
2 |
3 | import static java.lang.annotation.ElementType.TYPE;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.Target;
8 |
9 | import org.apiguardian.api.API;
10 | import org.apiguardian.api.API.Status;
11 | import org.springframework.boot.test.context.SpringBootTest;
12 |
13 | import com.github.tomakehurst.wiremock.WireMockServer;
14 |
15 | import de.skuzzle.springboot.test.wiremock.stubs.Auth;
16 | import de.skuzzle.springboot.test.wiremock.stubs.HttpStub;
17 | import de.skuzzle.springboot.test.wiremock.stubs.Request;
18 |
19 | /**
20 | * Configures a WireMock server that is integrated with the Spring ApplicationContext. Use
21 | * in conjunction with any Spring Boot test annotation like {@link SpringBootTest}. You
22 | * should configure {@link #injectHttpHostInto()} resp. {@link #injectHttpsHostInto()} in
23 | * order to have the mock's random base url injected into a Spring-Boot application
24 | * property.
25 | *
26 | * By default, the mock server only serves unencrypted HTTP. If you want to test encrypted
27 | * traffic using SSL, you need to either specify
28 | * {@link #randomHttpsPort()}=true or {@link #fixedHttpsPort()} with a value
29 | * >= 0.
30 | *
31 | * The configured {@link WireMockServer} instance is made available in the application
32 | * context and thus can easily be injected into a test class like this:
33 | *
34 | *
35 | * @Autowired
36 | * private WireMockServer wiremock;
37 | *
38 | *
39 | * Subsequently you can use it to configure your stubs if you refrain from using
40 | * {@link HttpStub annotation based} stubbing. Note that you should not call any lifecycle
41 | * methods like {@link WireMockServer#stop()} on the injected instance. The lifecycle will
42 | * be managed by this framework internally.
43 | *
44 | * @author Simon Taddiken
45 | * @see TestKeystores
46 | * @see HttpStub
47 | */
48 | @API(status = Status.EXPERIMENTAL)
49 | @Retention(RUNTIME)
50 | @Target(TYPE)
51 | public @interface WithWiremock {
52 |
53 | static final int DEFAULT_HTTP_PORT = 0;
54 | static final int DEFAULT_HTTPS_PORT = -1;
55 |
56 | /**
57 | * The names of the application properties that will be added and contain the
58 | * wiremock's http url.
59 | */
60 | String[] injectHttpsHostInto() default "";
61 |
62 | /**
63 | * The names of the application properties that will be added and contain the
64 | * wiremock's https url.
65 | */
66 | String[] injectHttpHostInto() default "";
67 |
68 | /**
69 | * Whether client authentication (via SSL client certificate) is required. When
70 | * {@link #truststoreLocation()} is not configured then the mock server trusts the
71 | * single certificate that can be retrieved using
72 | * {@link TestKeystores#TEST_CLIENT_CERTIFICATE}.
73 | */
74 | boolean needClientAuth() default false;
75 |
76 | /**
77 | * Location of the keystore to use for server side SSL. Defaults to
78 | * {@link TestKeystores#TEST_SERVER_CERTIFICATE}. The location will be resolved using
79 | * {@link ClassLoader#getResource(String)}.
80 | */
81 | String keystoreLocation() default "certs/server_keystore.jks";
82 |
83 | /**
84 | * Type of the {@link #keystoreLocation() keystore}.
85 | */
86 | String keystoreType() default "JKS";
87 |
88 | /**
89 | * Password of the {@link #keystoreLocation() keystore}.
90 | */
91 | String keystorePassword() default "password";
92 |
93 | /**
94 | * Location for the trustsore to use for client side SSL. Defaults to
95 | * {@link TestKeystores#TEST_CLIENT_CERTIFICATE_TRUST}. The location will be resolved
96 | * using {@link ClassLoader#getResource(String)}.
97 | */
98 | String truststoreLocation() default "certs/server_truststore.jks";
99 |
100 | /**
101 | * Password of the {@link #truststoreLocation() truststore}.
102 | */
103 | String truststorePassword() default "password";
104 |
105 | /**
106 | * Type of the {@link #truststoreLocation() truststore}.
107 | */
108 | String truststoreType() default "JKS";
109 |
110 | /**
111 | * Disable HTTP and only serves HTTPS.
112 | */
113 | boolean sslOnly() default false;
114 |
115 | /**
116 | * Whether to use random HTTP port. Defaults to true but will be silently
117 | * ignored if {@link #fixedHttpPort()} is specified with a value > 0
118 | *
119 | * @since 0.0.15
120 | */
121 | boolean randomHttpPort() default true;
122 |
123 | /**
124 | * Enables HTTPS on a random port. Defaults to false. Mutual exclusive to
125 | * {@link #fixedHttpsPort()}.
126 | *
127 | * @since 0.0.15
128 | */
129 | boolean randomHttpsPort() default false;
130 |
131 | /**
132 | * Enables HTTP on a fixed port. If specified with a value > 0 the fixed port will
133 | * take precedence even if {@link #randomHttpPort()} is set to true.
134 | *
135 | * @since 0.0.15
136 | */
137 | int fixedHttpPort() default DEFAULT_HTTP_PORT;
138 |
139 | /**
140 | * Enables HTTPS on a fixed port. Mutual exclusive to {@link #randomHttpsPort()}.
141 | *
142 | * @since 0.0.15
143 | */
144 | int fixedHttpsPort() default DEFAULT_HTTPS_PORT;
145 |
146 | /**
147 | * Required authentication information that will be added to every stub which itself
148 | * doesn't specify {@link Request#authenticatedBy()}. Note that, once authentication
149 | * is configured on this level, you can not undo it for specific stubs.
150 | */
151 | Auth withGlobalAuthentication() default @Auth;
152 | }
153 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/main/java/de/skuzzle/springboot/test/wiremock/stubs/Request.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock.stubs;
2 |
3 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
4 |
5 | import java.lang.annotation.Retention;
6 |
7 | import org.apiguardian.api.API;
8 | import org.apiguardian.api.API.Status;
9 |
10 | import com.github.tomakehurst.wiremock.client.WireMock;
11 |
12 | /**
13 | * Defines the stub request that will be matched in order to produce a mock response. If
14 | * no attributes are specified, every request will be matched.
15 | *
16 | * Some attributes of this annotation support advanced matching instead of plain text
17 | * comparison. For example, {@code withBody = "containing:someString"} matches a body that
18 | * contains {@code "someString"}. There are multiple such operators that are supported:
19 | *
20 | *
21 | * Supported string matching operations
22 | *
23 | * | prefix |
24 | * operation |
25 | *
26 | *
27 | * eq: |
28 | * {@link WireMock#equalTo(String)} |
29 | *
30 | *
31 | * eqIgnoreCase: |
32 | * {@link WireMock#equalToIgnoreCase(String)} |
33 | *
34 | *
35 | * eqToJson: |
36 | * {@link WireMock#equalToJson(String)} |
37 | *
38 | *
39 | * eqToXml: |
40 | * {@link WireMock#equalToXml(String)} |
41 | *
42 | *
43 | * matching: |
44 | * {@link WireMock#matching(String)} |
45 | *
46 | *
47 | * notMatching: |
48 | * {@link WireMock#notMatching(String)} |
49 | *
50 | *
51 | * matchingXPath: |
52 | * {@link WireMock#matchingXPath(String)} |
53 | *
54 | *
55 | * matchingJsonPath: |
56 | * {@link WireMock#matchingJsonPath(String)} |
57 | *
58 | *
59 | * containing: |
60 | * {@link WireMock#containing(String)} |
61 | *
62 | *
63 | * With no prefix the string comparison defaults to eq:.
64 | *
65 | * @author Simon Taddiken
66 | * @see Response
67 | */
68 | @API(status = Status.EXPERIMENTAL)
69 | @Retention(RUNTIME)
70 | public @interface Request {
71 |
72 | // sentinel value to signal that no priority was configured
73 | static int NO_PRIORITY = Integer.MAX_VALUE - 1;
74 |
75 | /** The stub's priority. */
76 | int priority() default NO_PRIORITY;
77 |
78 | /**
79 | * Allows to configure WireMock scenarios that can be used for stateful request
80 | * matching.
81 | *
82 | * Note that this attribute must be left empty when used within a {@link HttpStub}
83 | * with multiple responses configured.
84 | */
85 | Scenario scenario() default @Scenario;
86 |
87 | /**
88 | * Authentication information required for the stub to match. By default, no
89 | * authentication is required.
90 | */
91 | Auth authenticatedBy() default @Auth;
92 |
93 | /** Request method for this stub. If not specified, every method will be matched. */
94 | String withMethod() default "ANY";
95 |
96 | /**
97 | * The URL for this stub. Mutual exclusive to {@link #toUrlPattern()},
98 | * {@link #toUrlPath()} and {@link #toUrlPathPattern()}. If not specified, every url
99 | * will be matched.
100 | *
101 | * Warning: Using {@link #toUrl()} in combination with {@link #withQueryParameters()}
102 | * will effectively result in a conflicting stub definition that will never match. Use
103 | * {@link #toUrlPath()} instead.
104 | */
105 | String toUrl() default "";
106 |
107 | String toUrlPattern() default "";
108 |
109 | /**
110 | * The URL path for this stub. Mutual exclusive to {@link #toUrlPattern()},
111 | * {@link #toUrl()} and {@link #toUrlPathPattern()}. If not specified, every url will
112 | * be matched.
113 | */
114 | String toUrlPath() default "";
115 |
116 | String toUrlPathPattern() default "";
117 |
118 | /**
119 | * The expected body to match. You can optionally prefix the string with a matching
120 | * operator like {@code containing:} or {@code matching:} By default, matches every
121 | * body.
122 | */
123 | String withBody() default "";
124 |
125 | /**
126 | * Headers to match. You can specify key/value pairs and optionally operators for
127 | * value matching like so:
128 | *
129 | *
130 | * containingHeaders = {
131 | * "If-None-Match=matching:[a-z0-9-]",
132 | * "Content-Type=application/json"
133 | * }
134 | *
135 | *
136 | * See the documentation for {@link Request} for a list of supported operators.
137 | */
138 | String[] containingHeaders() default {};
139 |
140 | /**
141 | * Cookies to match. You can specify key/value pairs and optionally operators for
142 | * value matching like so:
143 | *
144 | *
145 | * containingCookies = {
146 | * "jsessionId=matching:[a-z0-9]"
147 | * }
148 | *
149 | *
150 | * See the documentation for {@link Request} for a list of supported operators.
151 | */
152 | String[] containingCookies() default {};
153 |
154 | /**
155 | * Query parameters to match. You can specify key/value pairs and optionally operators
156 | * for value matching like so:
157 | *
158 | *
159 | * withQueryParameters = {
160 | * "search=eqIgnoreCase:searchterm",
161 | * "limit=100"
162 | * }
163 | *
164 | *
165 | * See the documentation for {@link Request} for a list of supported operators.
166 | *
167 | * Note: Doesn't work in combination with {@link #toUrl()} but you can use
168 | * {@link #toUrlPath()} instead. See related GitHub issue:
169 | * https://github.com/tomakehurst/wiremock/issues/1262
170 | */
171 | String[] withQueryParameters() default {};
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | de.skuzzle.springboot.test
6 | spring-boot-wiremock-parent
7 | 0.0.18
8 |
9 |
10 | spring-boot-wiremock
11 | SpringBoot WireMock
12 | Easily set up WireMock in your SpringBoot tests
13 |
14 |
15 |
16 |
17 | com.google.guava
18 | guava
19 |
20 |
21 | org.apiguardian
22 | apiguardian-api
23 |
24 |
25 | com.github.tomakehurst
26 | wiremock-jre8
27 | compile
28 |
29 |
30 | org.assertj
31 | assertj-core
32 | compile
33 |
34 |
35 | org.junit.jupiter
36 | junit-jupiter-api
37 | compile
38 |
39 |
40 | org.slf4j
41 | slf4j-api
42 | compile
43 |
44 |
45 | org.apache.httpcomponents
46 | httpclient
47 | test
48 |
49 |
50 | org.apache.httpcomponents
51 | httpcore
52 | test
53 |
54 |
55 |
56 |
57 | org.springframework
58 | spring-context
59 |
60 |
61 | org.springframework
62 | spring-core
63 |
64 |
65 | org.springframework
66 | spring-test
67 |
68 |
69 | org.springframework
70 | spring-beans
71 |
72 |
73 | org.springframework
74 | spring-web
75 | provided
76 |
77 |
78 |
79 | org.springframework.boot
80 | spring-boot-starter
81 | compile
82 |
83 |
84 | org.springframework.boot
85 | spring-boot
86 | compile
87 |
88 |
89 | org.springframework.boot
90 | spring-boot-test
91 | compile
92 |
93 |
94 | org.junit.vintage
95 | junit-vintage-engine
96 |
97 |
98 |
99 |
100 | org.springframework.boot
101 | spring-boot-autoconfigure
102 |
103 |
104 | org.junit.jupiter
105 | junit-jupiter-params
106 | test
107 |
108 |
109 | nl.jqno.equalsverifier
110 | equalsverifier
111 | test
112 |
113 |
114 |
115 |
116 |
117 |
118 | org.apache.maven.plugins
119 | maven-resources-plugin
120 | 3.2.0
121 |
122 |
123 |
124 | copy-resources
125 |
126 | validate
127 |
128 | ${basedir}/..
129 |
130 |
131 | ${basedir}/../readme/
132 | true
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | org.apache.maven.plugins
141 | maven-jar-plugin
142 |
143 |
144 |
145 | de.skuzzle.springboot.test.wiremock
146 |
147 |
148 |
149 |
150 |
151 | maven-dependency-plugin
152 |
153 |
154 | org.springframework.boot:spring-boot-starter
155 |
156 |
157 |
158 |
159 |
160 |
161 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/main/java/de/skuzzle/springboot/test/wiremock/WiremockAnnotationConfiguration.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock;
2 |
3 | import java.lang.reflect.AnnotatedElement;
4 | import java.util.Arrays;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 | import java.util.Objects;
8 | import java.util.Optional;
9 | import java.util.stream.Stream;
10 |
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 | import org.springframework.core.annotation.MergedAnnotation;
14 | import org.springframework.core.annotation.MergedAnnotations;
15 | import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
16 |
17 | import com.github.tomakehurst.wiremock.WireMockServer;
18 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
19 | import com.google.common.base.Preconditions;
20 | import com.google.common.io.Resources;
21 |
22 | /**
23 | * Creates the {@link WireMockServer} from the values configured in {@link WithWiremock}
24 | * annotation.
25 | *
26 | * @author Simon Taddiken
27 | */
28 | final class WiremockAnnotationConfiguration {
29 |
30 | private static final Logger log = LoggerFactory.getLogger(WiremockAnnotationConfiguration.class);
31 |
32 | private static final String SERVER_HTTP_HOST_PROPERTY = "wiremock.server.httpHost";
33 | private static final String SERVER_HTTPS_HOST_PROPERTY = "wiremock.server.httpsHost";
34 | private static final String SERVER_HTTP_PORT_PROPERTY = "wiremock.server.httpPort";
35 | private static final String SERVER_HTTPS_PORT_PROPERTY = "wiremock.server.httpsPort";
36 |
37 | private final WithWiremock wwm;
38 |
39 | private WiremockAnnotationConfiguration(WithWiremock wwm) {
40 | Preconditions.checkArgument(wwm != null, "WithWiremock annotation must not be null");
41 | this.wwm = wwm;
42 | }
43 |
44 | public static WiremockAnnotationConfiguration from(WithWiremock wwm) {
45 | return new WiremockAnnotationConfiguration(wwm);
46 | }
47 |
48 | public static Optional fromAnnotatedElement(AnnotatedElement source) {
49 | return MergedAnnotations
50 | .from(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
51 | .stream(WithWiremock.class)
52 | .map(MergedAnnotation::synthesize)
53 | .findFirst()
54 | .map(WiremockAnnotationConfiguration::from);
55 | }
56 |
57 | @Override
58 | public int hashCode() {
59 | return Objects.hash(wwm);
60 | }
61 |
62 | @Override
63 | public boolean equals(Object obj) {
64 | return obj == this || obj instanceof WiremockAnnotationConfiguration
65 | && Objects.equals(wwm, ((WiremockAnnotationConfiguration) obj).wwm);
66 | }
67 |
68 | public WithWiremock annotation() {
69 | return this.wwm;
70 | }
71 |
72 | public Stream getInjectHttpHostPropertyNames() {
73 | return Stream.concat(Arrays.stream(wwm.injectHttpHostInto()), Stream.of(SERVER_HTTP_HOST_PROPERTY));
74 | }
75 |
76 | public Stream getInjectHttpsHostPropertyNames() {
77 | return Stream.concat(Arrays.stream(wwm.injectHttpsHostInto()), Stream.of(SERVER_HTTPS_HOST_PROPERTY));
78 | }
79 |
80 | private int httpPort() {
81 | // NOTE: for HTTP (in contrast to HTTPS), the fixed port takes precedence over
82 | // random port. (Otherwise one would need to specify both randomHttpPort = false
83 | // AND fixedHttpPort = 1337 to use a fixed port (because randomHttpPort defaults
84 | // to true)
85 | if (wwm.fixedHttpPort() != WithWiremock.DEFAULT_HTTP_PORT) {
86 | return wwm.fixedHttpPort();
87 | }
88 | if (wwm.randomHttpPort()) {
89 | Preconditions.checkArgument(wwm.fixedHttpPort() == 0,
90 | "Inconsistent HTTP port configuration. Either configure 'randomHttpPort' OR 'fixedHttpPort'");
91 | return 0;
92 | }
93 | return wwm.fixedHttpPort();
94 | }
95 |
96 | private int httpsPort() {
97 | if (wwm.randomHttpsPort()) {
98 | Preconditions.checkArgument(wwm.fixedHttpsPort() == WithWiremock.DEFAULT_HTTPS_PORT,
99 | "Inconsistent HTTPS port configuration. Either configure 'randomHttpsPort' OR 'fixedHttpsPort'");
100 | return 0;
101 | }
102 | return wwm.fixedHttpsPort();
103 | }
104 |
105 | public WireMockServer createWireMockServer() {
106 | return new WireMockServer(createWiremockConfig());
107 | }
108 |
109 | public Map determineInjectionPropertiesFrom(WireMockServer wiremockServer) {
110 | final boolean isHttpEnabled = !wiremockServer.getOptions().getHttpDisabled();
111 | final boolean isHttpsEnabled = wiremockServer.getOptions().httpsSettings().enabled();
112 | final boolean sslOnly = wwm.sslOnly();
113 | Preconditions.checkArgument(isHttpsEnabled || !sslOnly,
114 | "WireMock configured for 'sslOnly' but with HTTPS disabled. Configure httpsPort with value >= 0");
115 | Preconditions.checkArgument(isHttpEnabled || isHttpsEnabled,
116 | "WireMock configured with disabled HTTP and disabled HTTPS. Please configure either httpPort or httpsPort with a value >= 0");
117 |
118 | final Map props = new HashMap<>();
119 | if (isHttpEnabled) {
120 | final String httpHost = String.format("http://localhost:%d", wiremockServer.port());
121 | getInjectHttpHostPropertyNames()
122 | .forEach(propertyName -> props.put(propertyName, httpHost));
123 | props.put(SERVER_HTTP_PORT_PROPERTY, "" + wiremockServer.port());
124 | }
125 |
126 | if (isHttpsEnabled) {
127 | final String httpsHost = String.format("https://localhost:%d", wiremockServer.httpsPort());
128 | getInjectHttpsHostPropertyNames()
129 | .forEach(propertyName -> props.put(propertyName, httpsHost));
130 | props.put(SERVER_HTTPS_PORT_PROPERTY, "" + wiremockServer.httpsPort());
131 | }
132 | return props;
133 | }
134 |
135 | private WireMockConfiguration createWiremockConfig() {
136 | final boolean needClientAuth = wwm.needClientAuth();
137 | final boolean sslOnly = wwm.sslOnly();
138 | final int httpPort = httpPort();
139 | log.debug("Determined {} as HTTP port from {}", httpPort, wwm);
140 | final int httpsPort = httpsPort();
141 | log.debug("Determined {} as HTTPS port from {}", httpsPort, wwm);
142 |
143 | final String keystoreLocation = getResource(wwm.keystoreLocation());
144 | final String keystorePassword = wwm.keystorePassword();
145 | final String keystoreType = wwm.keystoreType();
146 |
147 | final String truststoreLocation = getResource(wwm.truststoreLocation());
148 | final String truststorePassword = wwm.truststorePassword();
149 | final String truststoreType = wwm.truststoreType();
150 |
151 | final WireMockConfiguration configuration = new WireMockConfiguration()
152 | .needClientAuth(needClientAuth)
153 | .httpDisabled(sslOnly)
154 | .port(httpPort)
155 | .httpsPort(httpsPort);
156 | if (keystoreLocation != null) {
157 | configuration
158 | .keystorePath(keystoreLocation)
159 | .keystorePassword(keystorePassword)
160 | .keystoreType(keystoreType);
161 | }
162 | if (truststoreLocation != null) {
163 | configuration
164 | .trustStorePath(truststoreLocation)
165 | .trustStorePassword(truststorePassword)
166 | .trustStoreType(truststoreType);
167 | }
168 | return configuration;
169 | }
170 |
171 | private String getResource(String location) {
172 | if (location.isEmpty()) {
173 | return null;
174 | }
175 | return Resources.getResource(location).toString();
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/spring-boot-wiremock/src/main/java/de/skuzzle/springboot/test/wiremock/StubTranslator.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.springboot.test.wiremock;
2 |
3 | import java.util.Arrays;
4 | import java.util.Iterator;
5 | import java.util.function.BiConsumer;
6 |
7 | import com.github.tomakehurst.wiremock.WireMockServer;
8 | import com.github.tomakehurst.wiremock.client.MappingBuilder;
9 | import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
10 | import com.github.tomakehurst.wiremock.client.WireMock;
11 | import com.github.tomakehurst.wiremock.matching.StringValuePattern;
12 | import com.github.tomakehurst.wiremock.matching.UrlPattern;
13 | import com.google.common.base.Preconditions;
14 |
15 | import de.skuzzle.springboot.test.wiremock.stubs.Auth;
16 | import de.skuzzle.springboot.test.wiremock.stubs.HttpStub;
17 | import de.skuzzle.springboot.test.wiremock.stubs.Request;
18 | import de.skuzzle.springboot.test.wiremock.stubs.Response;
19 | import de.skuzzle.springboot.test.wiremock.stubs.Scenario;
20 | import de.skuzzle.springboot.test.wiremock.stubs.WrapAround;
21 |
22 | /**
23 | * Translates the {@link HttpStub} instance into a WireMock stub.
24 | *
25 | * @author Simon Taddiken
26 | */
27 | class StubTranslator {
28 |
29 | static void configureStubOn(WireMockServer wiremock, WithWiremock withWiremock, HttpStub stub) {
30 | final boolean multipleResponseStubs = stub.respond().length > 1;
31 | Preconditions.checkArgument(!multipleResponseStubs ||
32 | nullIfEmpty(stub.onRequest().scenario().name()) == null,
33 | "Scenario not supported within stub with multiple responses");
34 |
35 | final Iterator responses = Arrays.asList(stub.respond()).iterator();
36 |
37 | final WrapAround wrapAround = stub.onLastResponse();
38 | int state = 0;
39 | while (responses.hasNext()) {
40 | final Response response = responses.next();
41 |
42 | final MappingBuilder requestBuilder = buildRequest(withWiremock, stub.onRequest());
43 |
44 | if (multipleResponseStubs) {
45 | final String scenarioName = stub.toString();
46 |
47 | final int nextState = wrapAround.determineNextState(state, responses.hasNext());
48 |
49 | requestBuilder.inScenario(scenarioName)
50 | .whenScenarioStateIs(translateState(state))
51 | .willSetStateTo(translateState(nextState));
52 | }
53 |
54 | final ResponseDefinitionBuilder responseBuilder = buildResponse(response);
55 | wiremock.stubFor(requestBuilder.willReturn(responseBuilder));
56 | ++state;
57 | }
58 | }
59 |
60 | private static String translateState(int state) {
61 | return state == 0
62 | ? com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED
63 | : "" + state;
64 | }
65 |
66 | private static MappingBuilder buildRequest(WithWiremock withWiremock, Request request) {
67 | final String toUrl = nullIfEmpty(request.toUrl());
68 | final String toUrlPattern = nullIfEmpty(request.toUrlPattern());
69 | final String toUrlPath = nullIfEmpty(request.toUrlPath());
70 | final String toUrlPathPattern = nullIfEmpty(request.toUrlPathPattern());
71 | mutuallyExclusive(
72 | parameters("url", "urlPattern", "urlPath", "urlPathPattern"),
73 | values(toUrl, toUrlPattern, toUrlPath, toUrlPathPattern));
74 | final UrlPattern urlPattern = UrlPattern.fromOneOf(toUrl, toUrlPattern, toUrlPath, toUrlPathPattern);
75 |
76 | final MappingBuilder requestBuilder = WireMock.request(request.withMethod(), urlPattern);
77 |
78 | final Scenario scenario = request.scenario();
79 | final String scenarioName = nullIfEmpty(scenario.name());
80 | if (scenarioName != null) {
81 | final String scenarioState = nullIfEmpty(scenario.state());
82 | final String nextState = defaultIfEmpty(scenario.nextState(), scenarioName);
83 | requestBuilder.inScenario(scenarioName)
84 | .whenScenarioStateIs(scenarioState)
85 | .willSetStateTo(nextState);
86 | }
87 |
88 | parseValueArray(request.containingHeaders(), requestBuilder::withHeader);
89 | parseValueArray(request.withQueryParameters(), requestBuilder::withQueryParam);
90 | parseValueArray(request.containingCookies(), requestBuilder::withCookie);
91 |
92 | if (!configureAuthentication(requestBuilder, request.authenticatedBy())) {
93 | configureAuthentication(requestBuilder, withWiremock.withGlobalAuthentication());
94 | }
95 |
96 | final String requestBody = nullIfEmpty(request.withBody());
97 | if (requestBody != null) {
98 | requestBuilder.withRequestBody(StringValuePatterns.parseFromPrefix(requestBody));
99 | }
100 |
101 | final int priority = request.priority();
102 | if (priority != Request.NO_PRIORITY) {
103 | requestBuilder.atPriority(priority);
104 | }
105 |
106 | return requestBuilder;
107 | }
108 |
109 | private static boolean configureAuthentication(MappingBuilder mappingBuilder, Auth authenticatedBy) {
110 | final String basicAuthUsername = nullIfEmpty(authenticatedBy.basicAuthUsername());
111 | final String basicAuthPassword = nullIfEmpty(authenticatedBy.basicAuthPassword());
112 | final String bearerToken = nullIfEmpty(authenticatedBy.bearerToken());
113 | mutuallyExclusive(parameters("basicAuthPassword", "bearerToken"), values(basicAuthPassword, bearerToken));
114 | mutuallyExclusive(parameters("basicAuthUsername", "bearerToken"), values(basicAuthUsername, bearerToken));
115 |
116 | if (basicAuthUsername != null && basicAuthPassword != null) {
117 | mappingBuilder.withBasicAuth(basicAuthUsername, basicAuthPassword);
118 | return true;
119 | } else if (bearerToken != null) {
120 | mappingBuilder.withHeader("Authorization", WireMock.equalToIgnoreCase("Bearer " + bearerToken));
121 | return true;
122 | }
123 |
124 | return false;
125 | }
126 |
127 | private static ResponseDefinitionBuilder buildResponse(Response response) {
128 | final ResponseDefinitionBuilder responseBuilder = WireMock.aResponse()
129 | .withStatus(response.withStatus().value());
130 |
131 | final String statusMessage = nullIfEmpty(response.withStatusMessage());
132 | if (statusMessage != null) {
133 | responseBuilder.withStatusMessage(statusMessage);
134 | }
135 |
136 | final String body = nullIfEmpty(response.withBody());
137 | final String jsonBody = nullIfEmpty(response.withJsonBody());
138 | final String bodyBase64 = nullIfEmpty(response.withBodyBase64());
139 | final String bodyFile = nullIfEmpty(response.withBodyFile());
140 | mutuallyExclusive(
141 | parameters("body", "bodyBase64", "bodyFile", "jsonBody"),
142 | values(body, bodyBase64, bodyFile, jsonBody));
143 |
144 | if (body != null) {
145 | responseBuilder.withBody(body);
146 | } else if (bodyBase64 != null) {
147 | responseBuilder.withBase64Body(bodyBase64);
148 | } else if (bodyFile != null) {
149 | responseBuilder.withBodyFile(bodyFile);
150 | } else if (jsonBody != null) {
151 | responseBuilder
152 | .withBody(jsonBody)
153 | .withHeader("Content-Type", "application/json");
154 | }
155 |
156 | final String[] responseHeaders = response.withHeaders();
157 | for (final String headerAndValue : responseHeaders) {
158 | final String[] parts = headerAndValue.split("=", 2);
159 | responseBuilder.withHeader(parts[0], parts[1]);
160 | }
161 |
162 | final String responseContentType = nullIfEmpty(response.withContentType());
163 | if (responseContentType != null) {
164 | responseBuilder.withHeader("Content-Type", responseContentType);
165 | }
166 | return responseBuilder;
167 | }
168 |
169 | private static void parseValueArray(String[] array, BiConsumer builder) {
170 | for (final String keyAndValue : array) {
171 | final String[] parts = keyAndValue.split("=", 2);
172 | final String key = parts[0];
173 | final String valueWithPrefix = parts[1];
174 | final StringValuePattern valuePattern = StringValuePatterns.parseFromPrefix(valueWithPrefix);
175 | builder.accept(key, valuePattern);
176 | }
177 | }
178 |
179 | private static String defaultIfEmpty(String s, String defaultValue) {
180 | return s == null || s.isEmpty() ? defaultValue : s;
181 | }
182 |
183 | private static String nullIfEmpty(String s) {
184 | return s == null || s.isEmpty() ? null : s;
185 | }
186 |
187 | private static void mutuallyExclusive(String[] names, Object[] args) {
188 | final long notNullCount = Arrays.stream(args).filter(arg -> arg != null).count();
189 | Preconditions.checkArgument(notNullCount <= 1,
190 | "Parameters '%s' are mutually exclusive and only one must be specified", Arrays.toString(names));
191 | }
192 |
193 | private static String[] parameters(String... names) {
194 | return names;
195 | }
196 |
197 | private static Object[] values(Object... values) {
198 | return values;
199 | }
200 |
201 | }
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://search.maven.org/artifact/de.skuzzle.springboot.test/spring-boot-wiremock/0.0.18/jar)
4 | [](http://www.javadoc.io/doc/de.skuzzle.springboot.test/spring-boot-wiremock/0.0.18)
5 | [](https://coveralls.io/github/skuzzle/spring-boot-wiremock?branch=main)
6 | [](https://twitter.com/skuzzleOSS)
7 |
8 | # spring-boot-wiremock
9 | _This is **not** an official extension from the Spring Team!_ (Though one exists as part of the
10 | [spring-cloud](https://cloud.spring.io/spring-cloud-contract/reference/html/project-features.html#features-wiremock)
11 | project).
12 |
13 | _The easiest way to setup a [WireMock](http://wiremock.org/) server in your Spring-Boot tests._
14 | - [x] Run WireMock server on random port
15 | - [x] Inject WireMock hosts (http and https) as spring application property
16 | - [x] Easily setup server- and client side SSL
17 | - [x] Declarative stubs using annotations
18 |
19 | ```xml
20 |
21 | de.skuzzle.springboot.test
22 | spring-boot-wiremock
23 | 0.0.18
24 | test
25 |
26 | ```
27 |
28 | ```
29 | testImplementation 'de.skuzzle.springboot.test:spring-boot-wiremock:0.0.18'
30 | ```
31 |
32 | ## Quick start
33 | All you need to do is to add the `@WithWiremock` annotation to your Spring-Boot test. The annotation has some
34 | configuration options but the most notable one is `injectHttpHostInto`.
35 |
36 | ```java
37 | @SpringBootTest
38 | @WithWiremock(injectHttpHostInto = "your.application.serviceUrl")
39 | public class WiremockTest {
40 |
41 | @Value("${your.application.serviceUrl}")
42 | private String serviceUrl;
43 | @Autowired
44 | private WireMockServer wiremock;
45 |
46 | @Test
47 | void testWithExplicitStub() throws Exception {
48 | // Use standard WireMock API for minimum coupling to this library
49 | wiremock.stubFor(WireMock.get("/")
50 | .willReturn(aResponse().withStatus(200)));
51 |
52 | final ResponseEntity