Last active
November 22, 2017 02:37
-
-
Save vicly/eb248adbefd1fb5d37e7b5380c8c68ed to your computer and use it in GitHub Desktop.
[Test API Client with Mock Server] #REST #Test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// testCompile "junit:junit:4.12" | |
// testCompile "org.assertj:assertj-core:3.5.2" | |
// testCompile "com.github.tomakehurst:wiremock-standalone:2.7.1" | |
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; | |
import static com.github.tomakehurst.wiremock.client.WireMock.delete; | |
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; | |
import static com.github.tomakehurst.wiremock.client.WireMock.post; | |
import static com.github.tomakehurst.wiremock.client.WireMock.put; | |
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; | |
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; | |
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; | |
import java.util.List; | |
import javax.ws.rs.client.Client; | |
import javax.ws.rs.client.ClientBuilder; | |
import javax.ws.rs.client.WebTarget; | |
import org.apache.commons.lang3.RandomUtils; | |
import org.glassfish.jersey.client.ClientConfig; | |
import org.glassfish.jersey.logging.LoggingFeature; | |
import org.junit.After; | |
import org.junit.Before; | |
import org.junit.ClassRule; | |
import com.fasterxml.jackson.core.JsonProcessingException; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; | |
import com.github.tomakehurst.wiremock.junit.WireMockRule; | |
import com.github.tomakehurst.wiremock.stubbing.StubMapping; | |
import com.google.common.primitives.Ints; | |
/** | |
* Abstract test used to test API client. | |
* | |
* 1. initialises a mock server stub | |
* 2. provides helper methods to mock service. | |
* | |
* @author Vic Liu | |
*/ | |
public abstract class AbstractClientTest | |
{ | |
/** | |
* Singleton, shared cross multiple test classes, DO NOT call {@code WIREMOCK} in java {@code static} block, but only | |
* in your instance level test methods. | |
*/ | |
@ClassRule | |
public static final WireMockRule WIREMOCK = new WireMockRule( | |
wireMockConfig().dynamicPort()); | |
private static final ObjectMapper MAPPER = new ObjectMapper(); | |
private static final Object CLIENT_LOCK = new Object(); | |
protected Client client; | |
/** | |
* the mock server stub | |
*/ | |
protected WebTarget baseTarget; | |
/** | |
* Do not override this method. | |
* | |
* @see #beforeEachTest | |
*/ | |
@Before | |
public void setUp() | |
{ | |
if (client == null) | |
{ | |
synchronized (CLIENT_LOCK) | |
{ | |
int port = WIREMOCK.port(); | |
this.client = ClientBuilder.newClient(new ClientConfig().register(LoggingFeature.class)); | |
this.baseTarget = this.client.target("http://localhost:" + port); | |
afterWebTargetInit(this.baseTarget); | |
} | |
} | |
beforeEachTest(); | |
} | |
protected void afterWebTargetInit(WebTarget baseTarget) | |
{ | |
// do nothing by default | |
} | |
protected void beforeEachTest() | |
{ | |
// do nothing by default | |
} | |
/** | |
* Do not override this method. | |
* | |
* @see #afterEachTest | |
*/ | |
@After | |
public void tearDown() | |
{ | |
afterEachTest(); | |
} | |
protected void afterEachTest() | |
{ | |
// do nothing by default | |
} | |
protected StubMapping simplePostStub(String path, int responseStatus) | |
{ | |
return simplePostStub(path, responseStatus, null); | |
} | |
/** | |
* helper to stub POST with give response. | |
* | |
* @param path relative path | |
* @param responseStatus http response status code | |
* @param responseBody http response entity object, will be convert to JSON string | |
* @return StubMapping instance | |
*/ | |
protected StubMapping simplePostStub(String path, int responseStatus, Object responseBody) | |
{ | |
ResponseDefinitionBuilder responseBuilder = aResponse().withStatus(responseStatus); | |
if (responseBody != null) | |
{ | |
responseBuilder | |
.withHeader("Content-Type", "application/json") | |
.withBody(toJson(responseBody)); | |
} | |
return stubFor(post(urlEqualTo(path)) | |
.withHeader("Accept", equalTo("application/json")) | |
.willReturn(responseBuilder)); | |
} | |
/** | |
* helper to stub PUT with give response. | |
* | |
* @param path relative path | |
* @param responseStatus http response status code | |
* @param responseBody http response entity object, will be convert to JSON string | |
* @return StubMapping instance | |
*/ | |
protected StubMapping simplePutStub(String path, int responseStatus, Object responseBody) | |
{ | |
ResponseDefinitionBuilder responseBuilder = aResponse().withStatus(responseStatus); | |
if (responseBody != null) | |
{ | |
responseBuilder | |
.withHeader("Content-Type", "application/json") | |
.withBody(toJson(responseBody)); | |
} | |
return stubFor(put(urlEqualTo(path)) | |
.withHeader("Accept", equalTo("application/json")) | |
.willReturn(responseBuilder)); | |
} | |
/** | |
* helper to stub DELETE with give response. | |
* | |
* @param path relative path | |
* @param responseStatus http response status code | |
* @param responseBody http response entity object, will be convert to JSON string | |
* @return StubMapping instance | |
*/ | |
protected StubMapping simpleDeleteStub(String path, int responseStatus, Object responseBody) | |
{ | |
ResponseDefinitionBuilder responseBuilder = aResponse().withStatus(responseStatus); | |
if (responseBody != null) | |
{ | |
responseBuilder | |
.withHeader("Content-Type", "application/json") | |
.withBody(toJson(responseBody)); | |
} | |
return stubFor(delete(urlEqualTo(path)) | |
.withHeader("Accept", equalTo("application/json")) | |
.willReturn(responseBuilder)); | |
} | |
/** | |
* convert object to JSON string. | |
* | |
* @param val object | |
* @return JSON string | |
*/ | |
protected static String toJson(Object val) | |
{ | |
try | |
{ | |
return MAPPER.writeValueAsString(val); | |
} | |
catch (JsonProcessingException e) | |
{ | |
throw new RuntimeException("Failed to construct test json", e); | |
} | |
} | |
/** | |
* generate random http status not in the specified list. | |
* | |
* @param excludes what to be excluded. | |
* @return a random number between 100 and 599 | |
*/ | |
protected static int randomHttpStatusNotIn(int... excludes) | |
{ | |
List<Integer> list = Ints.asList(excludes); | |
while (true) | |
{ | |
int status = RandomUtils.nextInt(100, 599); | |
if (!list.contains(status)) | |
{ | |
return status; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SampleTest | |
extends AbstractClientTest | |
{ | |
@Rule | |
public ExpectedException thrown = ExpectedException.none(); | |
private XyzApiClient xyzApiClient; | |
@Override | |
protected void afterWebTargetInit(WebTarget baseTarget) | |
{ | |
// init client object | |
super.afterWebTargetInit(baseTarget); | |
this.xyzApiClient = new JaxRsXyzClient(this.baseTarget); | |
} | |
@Test | |
public void test200() | |
{ | |
// api path | |
String path = "/xyz"; | |
// test data | |
String xyz = "a string"; | |
// mock service | |
simplePostStub(path, 200, createResponse(xyz)); | |
// run | |
Xyz xyz = callApi(); | |
// verify | |
assertThat(xyz).isNotNull(); | |
assertThat(xyz.getValue()).isEqualTo(xyz); | |
verify(postRequestedFor(urlPathEqualTo(path))); | |
} | |
@Test | |
public void test400() | |
{ | |
simplePostStub("/", 400); | |
this.thrown.expect(XyzException.class); | |
this.thrown.expectMessage(startsWith("Error posting xyz")); | |
callApi(); | |
} | |
private Xyz callApi() | |
{ | |
// invoke api | |
XyzRequest fakeRequest = ...; | |
return this.xyzApiClient.post(fakeRequest); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment