Skip to content

Commit

Permalink
Closes #1614 - proper support for subclassing of the JUnit5 WireMockE…
Browse files Browse the repository at this point in the history
…xtension
  • Loading branch information
Tom Akehurst committed Nov 22, 2021
1 parent 60e9e85 commit bf048fa
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 4 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ dependencies {

testImplementation "junit:junit:4.13"
testImplementation("org.junit.jupiter:junit-jupiter:$versions.junitJupiter")
testImplementation("org.junit.platform:junit-platform-testkit")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
testImplementation("org.junit.platform:junit-platform-launcher")
testImplementation 'org.mockito:mockito-junit-jupiter:4.0.0'
Expand Down
51 changes: 50 additions & 1 deletion docs-v2/_docs/junit-jupiter.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,53 @@ public class JUnitJupiterProgrammaticProxyTest {
}
}
}
```
```

## Subclassing the extension

Like the JUnit 4.x rule, `WireMockExtension` can be subclassed in order to extend its behaviour by hooking into its lifecycle events.
This can also be a good approach for creating a domain-specific API mock, by adding methods to stub and verify specific calls.

```java
public class MyMockApi extends WireMockExtension {

public MyMockApi(WireMockExtension.Builder builder) {
super(builder);
}

@Override
protected void onBeforeAll(WireMockRuntimeInfo wireMockRuntimeInfo) {
// Do things before any tests have run
}

@Override
protected void onBeforeEach(WireMockRuntimeInfo wireMockRuntimeInfo) {
// Do things before each test
}

@Override
protected void onAfterEach(WireMockRuntimeInfo wireMockRuntimeInfo) {
// Do things after each test
}

@Override
protected void onAfterAll(WireMockRuntimeInfo wireMockRuntimeInfo) {
// Do things after all tests have run
}
}
```

Note the constructor, which takes the extension's builder as its parameter. By making this public, you can pass an instance
of the builder in when constructing your extension as follows:

```java
@RegisterExtension
static MyMockApi myMockApi =
new MyMockApi(
WireMockExtension.extensionOptions()
.options(wireMockConfig().dynamicPort().dynamicHttpsPort())
.configureStaticDsl(true));
```

This will ensure that all parameters from the builder will be set as they would if you had constructed an instance of
`WireMockExtension` from it.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class WireMockExtension extends DslWrapper

private Options options;
private WireMockServer wireMockServer;
private WireMockRuntimeInfo runtimeInfo;
private boolean isNonStatic = false;

private Boolean proxyMode;
Expand All @@ -48,8 +49,14 @@ public WireMockExtension() {
failOnUnmatchedRequests = false;
}

// Intended to be called from the builder
protected WireMockExtension(
protected WireMockExtension(Builder builder) {
this.options = builder.options;
this.configureStaticDsl = builder.configureStaticDsl;
this.failOnUnmatchedRequests = builder.failOnUnmatchedRequests;
this.proxyMode = builder.proxyMode;
}

private WireMockExtension(
Options options,
boolean configureStaticDsl,
boolean failOnUnmatchedRequests,
Expand All @@ -60,10 +67,22 @@ protected WireMockExtension(
this.proxyMode = proxyMode;
}

public static Builder extensionOptions() {
return newInstance();
}

public static Builder newInstance() {
return new Builder();
}

protected void onBeforeAll(WireMockRuntimeInfo wireMockRuntimeInfo) {}

protected void onBeforeEach(WireMockRuntimeInfo wireMockRuntimeInfo) {}

protected void onAfterEach(WireMockRuntimeInfo wireMockRuntimeInfo) {}

protected void onAfterAll(WireMockRuntimeInfo wireMockRuntimeInfo) {}

@Override
public boolean supportsParameter(
final ParameterContext parameterContext, final ExtensionContext extensionContext)
Expand All @@ -77,7 +96,7 @@ public Object resolveParameter(
throws ParameterResolutionException {

if (parameterIsWireMockRuntimeInfo(parameterContext)) {
return new WireMockRuntimeInfo(wireMockServer);
return runtimeInfo;
}

return null;
Expand All @@ -88,6 +107,8 @@ private void startServerIfRequired(ExtensionContext extensionContext) {
wireMockServer = new WireMockServer(resolveOptions(extensionContext));
wireMockServer.start();

runtimeInfo = new WireMockRuntimeInfo(wireMockServer);

this.admin = wireMockServer;
this.stubbing = wireMockServer;

Expand Down Expand Up @@ -147,6 +168,8 @@ private boolean parameterIsWireMockRuntimeInfo(ParameterContext parameterContext
public void beforeAll(ExtensionContext context) throws Exception {
startServerIfRequired(context);
setAdditionalOptions(context);

onBeforeAll(runtimeInfo);
}

@Override
Expand All @@ -163,11 +186,15 @@ public void beforeEach(ExtensionContext context) throws Exception {
if (proxyMode) {
JvmProxyConfigurer.configureFor(wireMockServer);
}

onBeforeEach(runtimeInfo);
}

@Override
public void afterAll(ExtensionContext context) throws Exception {
stopServerIfRunning();

onAfterAll(runtimeInfo);
}

@Override
Expand All @@ -183,6 +210,8 @@ public void afterEach(ExtensionContext context) throws Exception {
if (proxyMode) {
JvmProxyConfigurer.restorePrevious();
}

onAfterEach(runtimeInfo);
}

public WireMockRuntimeInfo getRuntimeInfo() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright (C) 2021 Thomas Akehurst
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.tomakehurst.wiremock.junit5;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static com.github.tomakehurst.wiremock.junit5.WireMockExtension.extensionOptions;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;

import com.github.tomakehurst.wiremock.http.HttpClientFactory;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Events;

public class JUnitJupiterExtensionSubclassingTest {

@Test
void executes_all_lifecycle_callbacks() {
Events testEvents =
EngineTestKit.engine("junit-jupiter")
.selectors(selectClass(TestClass.class))
.execute()
.testEvents();

testEvents.assertStatistics(stats -> stats.succeeded(1));

assertThat(MyWireMockExtension.beforeAllCalled, is(true));
assertThat(MyWireMockExtension.beforeEachCalled, is(true));
assertThat(MyWireMockExtension.afterEachCalled, is(true));
assertThat(MyWireMockExtension.afterAllCalled, is(true));
}

public static class TestClass {

CloseableHttpClient client;

@RegisterExtension
static MyWireMockExtension wm =
new MyWireMockExtension(
extensionOptions()
.options(wireMockConfig().dynamicPort().dynamicHttpsPort())
.configureStaticDsl(true));

@BeforeEach
void initEach() {
client = HttpClientFactory.createClient();
}

@Test
void respects_config_passed_via_builder() throws Exception {
assertThat(MyWireMockExtension.beforeAllCalled, is(true));
assertThat(MyWireMockExtension.beforeEachCalled, is(true));
assertThat(MyWireMockExtension.afterEachCalled, is(false));
assertThat(MyWireMockExtension.afterAllCalled, is(false));

stubFor(get("/ping").willReturn(ok()));

try (CloseableHttpResponse response =
client.execute(new HttpGet("https://localhost:" + wm.getHttpsPort() + "/ping"))) {
assertThat(response.getCode(), is(200));
}
}
}

public static class MyWireMockExtension extends WireMockExtension {

public static boolean beforeAllCalled = false;
public static boolean beforeEachCalled = false;
public static boolean afterEachCalled = false;
public static boolean afterAllCalled = false;

public MyWireMockExtension(WireMockExtension.Builder builder) {
super(builder);
}

@Override
protected void onBeforeAll(WireMockRuntimeInfo wireMockRuntimeInfo) {
beforeAllCalled = true;
}

@Override
protected void onBeforeEach(WireMockRuntimeInfo wireMockRuntimeInfo) {
beforeEachCalled = true;
}

@Override
protected void onAfterEach(WireMockRuntimeInfo wireMockRuntimeInfo) {
afterEachCalled = true;
}

@Override
protected void onAfterAll(WireMockRuntimeInfo wireMockRuntimeInfo) {
afterAllCalled = true;
}
}
}

0 comments on commit bf048fa

Please sign in to comment.