Skip to content

Commit 0571c8c

Browse files
committed
Introduce support for WireMock extensions
1 parent dfa9d6e commit 0571c8c

File tree

6 files changed

+240
-13
lines changed

6 files changed

+240
-13
lines changed

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ A common example is using Wiremock 3.x with Java 1.8.
1717

1818
## Usage
1919

20-
Import the dependency:
20+
### Importing the dependency
2121

2222
```xml
2323
<dependency>
@@ -28,8 +28,9 @@ Import the dependency:
2828
</dependency>
2929
```
3030

31-
Use it in your Unit tests.
32-
Javadoc is coming soon!
31+
### Using the test container in JUnit 4/5
32+
33+
P.S: Javadoc is coming soon!
3334

3435
```java
3536
import org.wiremock.integrations.testcontainers.WireMockContainer;
@@ -68,6 +69,17 @@ public class WireMockContainerTest {
6869
}
6970
```
7071

72+
### Using extensions
73+
74+
The API supports adding [WireMock extensions](https://wiremock.org/docs/extending-wiremock/)
75+
to the test container.
76+
The extension can be sourced from the classpath for bundled extensions,
77+
or added from the JAR file in the initializer.
78+
79+
#### Using external extension
80+
81+
For the , an extension Jar should be
82+
7183
## Contributing
7284

7385
All contributions are welcome!

pom.xml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,50 @@
103103
</execution>
104104
</executions>
105105
</plugin>
106+
107+
<!-- For testing WireMock extensions -->
108+
<plugin>
109+
<groupId>org.apache.maven.plugins</groupId>
110+
<artifactId>maven-dependency-plugin</artifactId>
111+
<version>3.5.0</version>
112+
<executions>
113+
<execution>
114+
<id>copy</id>
115+
<phase>package</phase>
116+
<goals>
117+
<goal>copy</goal>
118+
<goal>copy-dependencies</goal>
119+
</goals>
120+
<configuration>
121+
<artifactItems>
122+
<artifactItem>
123+
<groupId>com.ninecookies.wiremock.extensions</groupId>
124+
<artifactId>wiremock-extensions</artifactId>
125+
<version>0.4.1</version>
126+
<overWrite>false</overWrite>
127+
<destFileName>9cookies-wiremock-extensions.jar</destFileName>
128+
</artifactItem>
129+
</artifactItems>
130+
<excludeArtifactIds>testcontainers</excludeArtifactIds> <!--Looks like the library defect, it should be provided-->
131+
<outputDirectory>${project.build.directory}/test-wiremock-extension</outputDirectory>
132+
<overWriteReleases>false</overWriteReleases>
133+
<overWriteSnapshots>true</overWriteSnapshots>
134+
</configuration>
135+
</execution>
136+
</executions>
137+
</plugin>
106138
</plugins>
107139
</build>
108140

141+
<!-- For testing WireMock extensions -->
142+
<repositories>
143+
<repository>
144+
<id>9c-releases</id>
145+
<url>https://raw.github.com/9cookies/mvn-repo/master/releases/</url>
146+
<releases>
147+
<enabled>true</enabled>
148+
<updatePolicy>always</updatePolicy>
149+
</releases>
150+
</repository>
151+
</repositories>
109152
</project>

src/main/java/org/wiremock/integrations/testcontainers/WireMockContainer.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,16 @@
2121
import java.net.URISyntaxException;
2222
import java.net.URL;
2323
import java.nio.charset.StandardCharsets;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.util.ArrayList;
27+
import java.util.Collection;
28+
import java.util.Collections;
2429
import java.util.HashMap;
30+
import java.util.List;
2531
import java.util.Map;
32+
import java.util.stream.Collectors;
33+
import java.util.stream.Stream;
2634

2735
import org.testcontainers.containers.GenericContainer;
2836
import org.testcontainers.images.builder.Transferable;
@@ -31,6 +39,8 @@
3139

3240
/**
3341
* Provisions WireMock standalone server as a container.
42+
* Designed to follow the WireMock Docker image ({@code wiremock/wiremock}) structure and configuration,
43+
* but other images can be included too at your own risk.
3444
*/
3545
public class WireMockContainer extends GenericContainer<WireMockContainer> {
3646
private static final String DEFAULT_IMAGE_NAME = "wiremock/wiremock";
@@ -39,12 +49,15 @@ public class WireMockContainer extends GenericContainer<WireMockContainer> {
3949
private static final String MAPPINGS_DIR = "/home/wiremock/mappings/";
4050
private static final String FILES_DIR = "/home/wiremock/__files/";
4151

52+
private static final String EXTENSIONS_DIR = "/var/wiremock/extensions/";
53+
4254
private static final int PORT = 8080;
4355

4456
private final StringBuilder wireMockArgs;
4557

4658
private final Map<String, Stub> mappingStubs = new HashMap<>();
4759
private final Map<String, MountableFile> mappingFiles = new HashMap<>();
60+
private final Map<String, Extension> extensions = new HashMap<>();
4861

4962
public WireMockContainer() {
5063
this(DEFAULT_TAG);
@@ -115,6 +128,55 @@ public WireMockContainer withFileFromResource(String name, Class<?> resource, St
115128
return withFileFromResource(name, resource.getName().replace('.', '/') + "/" + filename);
116129
}
117130

131+
/**
132+
* Add extension that will be loaded from the specified JAR file.
133+
* @param id Unique ID of the extension, for logging purposes
134+
* @param classNames Class names of the extension to be included
135+
* @param jars JARs to be included into the container
136+
* @return this instance
137+
*/
138+
public WireMockContainer withExtension(String id, Collection<String> classNames, Collection<File> jars) {
139+
final Extension extension = new Extension(id);
140+
extension.extensionClassNames.addAll(classNames);
141+
extension.jars.addAll(jars);
142+
extensions.put(id, extension);
143+
return this;
144+
}
145+
146+
/**
147+
* Add extension that will be loaded from the specified directory with JAR files.
148+
* @param id Unique ID of the extension, for logging purposes
149+
* @param classNames Class names of the extension to be included
150+
* @param jarDirectory Directory that stores all JARs
151+
* @return this instance
152+
*/
153+
public WireMockContainer withExtension(String id, Collection<String> classNames, File jarDirectory) {
154+
final List<File> jarsInTheDirectory;
155+
try (Stream<Path> walk = Files.walk(jarDirectory.toPath())) {
156+
jarsInTheDirectory = walk
157+
.filter(p -> !Files.isDirectory(p))
158+
.map(Path::toFile)
159+
.filter(f -> f.toString().endsWith(".jar"))
160+
.collect(Collectors.toList());
161+
} catch (IOException e) {
162+
throw new IllegalArgumentException("Cannot list JARs in the directory " + jarDirectory, e);
163+
}
164+
165+
return withExtension(id, classNames, jarsInTheDirectory);
166+
}
167+
168+
/**
169+
* Add extension that will be loaded from the classpath.
170+
* This method can be used if the extension is a part of the WireMock bundle,
171+
* or a Jar is already added via {@link #withExtension(String, Collection, Collection)}}
172+
* @param id Unique ID of the extension, for logging purposes
173+
* @param className Class name of the extension
174+
* @return this instance
175+
*/
176+
public WireMockContainer withExtension(String id, String className) {
177+
return withExtension(id, Collections.singleton(className), Collections.emptyList());
178+
}
179+
118180
public String getEndpoint() {
119181
return String.format("http://%s:%d", getHost(), getMappedPort(PORT));
120182
}
@@ -131,14 +193,33 @@ public Integer getServerPort() {
131193
protected void configure() {
132194
super.configure();
133195
withExposedPorts(PORT);
134-
withCommand(wireMockArgs.toString());
135196
for (Stub stub : mappingStubs.values()) {
136197
withCopyToContainer(Transferable.of(stub.json), MAPPINGS_DIR + stub.name + ".json");
137198
}
138199

139200
for (Map.Entry<String, MountableFile> mount : mappingFiles.entrySet()) {
140201
withCopyToContainer(mount.getValue(), FILES_DIR + mount.getKey());
141202
}
203+
204+
final ArrayList<String> extensionClassNames = new ArrayList<>();
205+
for (Map.Entry<String, Extension> entry : extensions.entrySet()) {
206+
final Extension ext = entry.getValue();
207+
extensionClassNames.addAll(ext.extensionClassNames);
208+
for (File jar : ext.jars) {
209+
withCopyToContainer(MountableFile.forHostPath(jar.toPath()), EXTENSIONS_DIR + jar.getName());
210+
}
211+
}
212+
if (!extensionClassNames.isEmpty()) {
213+
wireMockArgs.append(" --extensions ");
214+
int counter = 0;
215+
for (String className : extensionClassNames) {
216+
wireMockArgs.append(className);
217+
wireMockArgs.append( (++counter != extensionClassNames.size()) ? ',' : ' ');
218+
}
219+
}
220+
221+
// Add CLI arguments
222+
withCommand(wireMockArgs.toString());
142223
}
143224

144225
private static final class Stub {
@@ -151,4 +232,14 @@ public Stub (String name, String json) {
151232
}
152233
}
153234

235+
private static final class Extension {
236+
final String id;
237+
final List<File> jars = new ArrayList<>();
238+
final List<String> extensionClassNames = new ArrayList<>();
239+
240+
public Extension(String id) {
241+
this.id = id;
242+
}
243+
}
244+
154245
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (C) 2023 WireMock Inc, Oleg Nenashev and all project contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.wiremock.integrations.testcontainers;
17+
18+
import org.junit.Before;
19+
import org.junit.Rule;
20+
import org.junit.Test;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import org.testcontainers.containers.output.Slf4jLogConsumer;
24+
25+
import java.net.http.HttpClient;
26+
import java.net.http.HttpRequest;
27+
import java.net.http.HttpResponse;
28+
import java.nio.file.Paths;
29+
import java.time.Duration;
30+
import java.util.Collections;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
34+
/**
35+
* Tests the WireMock extension loading.
36+
* It uses the external Jar supplied by the Maven Dependency Plugin.
37+
*/
38+
public class WireMockContainerExtensionTest {
39+
40+
public static final Logger LOGGER = LoggerFactory.getLogger(WireMockContainerExtensionTest.class);
41+
42+
@Rule
43+
public WireMockContainer wiremockServer = new WireMockContainer("2.35.0")
44+
.withStartupTimeout(Duration.ofSeconds(60))
45+
.withMapping("json-body-transformer", WireMockContainerExtensionTest.class, "json-body-transformer.json")
46+
.withExtension("JSON Body Transformer", Collections.singleton("com.ninecookies.wiremock.extensions.JsonBodyTransformer"),
47+
Collections.singleton(Paths.get("target", "test-wiremock-extension", "9cookies-wiremock-extensions.jar").toFile()));
48+
49+
@Before
50+
public void before() {
51+
wiremockServer.followOutput(new Slf4jLogConsumer(LOGGER));
52+
}
53+
54+
@Test
55+
public void testJSONBodyTransformer() throws Exception {
56+
final HttpClient client = HttpClient.newBuilder().build();
57+
final HttpRequest request = HttpRequest.newBuilder()
58+
.uri(wiremockServer.getRequestURI("json-body-transformer"))
59+
.timeout(Duration.ofSeconds(10))
60+
.header("Content-Type", "application/json")
61+
.POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"John Doe\"}")).build();
62+
63+
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
64+
65+
assertThat(response.body())
66+
.as("Wrong response body")
67+
.contains("Hello, John Doe!");
68+
}
69+
70+
}

src/test/java/org/wiremock/integrations/testcontainers/WireMockContainerTest.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,14 @@ public class WireMockContainerTest {
3535

3636
@Test
3737
public void helloWorld() throws Exception {
38-
final HttpClient client = HttpClient.newBuilder()
39-
.version(HttpClient.Version.HTTP_1_1)
40-
.build();
41-
42-
HttpRequest request = HttpRequest.newBuilder()
38+
final HttpClient client = HttpClient.newBuilder().build();
39+
final HttpRequest request = HttpRequest.newBuilder()
4340
.uri(wiremockServer.getRequestURI("hello"))
4441
.timeout(Duration.ofSeconds(10))
4542
.header("Content-Type", "application/json")
46-
.GET()
47-
.build();
43+
.GET().build();
4844

49-
HttpResponse<String> response =
50-
client.send(request, HttpResponse.BodyHandlers.ofString());
45+
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
5146

5247
assertThat(response.body())
5348
.as("Wrong response body")
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"request": {
3+
"method": "POST",
4+
"url": "/json-body-transformer"
5+
},
6+
"response": {
7+
"status": 201,
8+
"headers": {
9+
"content-type": "application/json"
10+
},
11+
"jsonBody": {
12+
"message": "Hello, $(name)!"
13+
},
14+
"transformers" : ["json-body-transformer"]
15+
}
16+
}

0 commit comments

Comments
 (0)