2121import java .net .URISyntaxException ;
2222import java .net .URL ;
2323import 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 ;
2429import java .util .HashMap ;
30+ import java .util .List ;
2531import java .util .Map ;
32+ import java .util .stream .Collectors ;
33+ import java .util .stream .Stream ;
2634
2735import org .testcontainers .containers .GenericContainer ;
2836import org .testcontainers .images .builder .Transferable ;
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 */
3545public 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}
0 commit comments