/* * Copyright (c) 2012 - 2016 Jadler contributors * This program is made available under the terms of the MIT License. */ package net.jadler; import java.nio.charset.Charset; import net.jadler.exception.JadlerException; import net.jadler.mocking.VerificationException; import net.jadler.mocking.Verifying; import net.jadler.stubbing.RequestStubbing; import net.jadler.stubbing.server.StubHttpServerManager; import net.jadler.stubbing.server.StubHttpServer; import net.jadler.stubbing.ResponseStubbing; /** * <p>This class is a gateway to the whole Jadler library. Jadler is a powerful yet simple to use * http mocking library for writing integration tests in the http environment. It provides a convenient way * to create a stub http server which serves all http requests sent during a test execution * by returning stub responses according to defined rules.</p> * * <h3>Jadler Usage Basics</h3> * * <p>Let's have a simple component with one operation: </p> * * <pre> * public interface AccountManager { * Account getAccount(String id); * } * </pre> * * <p>An implementation of the {@code getAccount} operation is supposed to send a GET http request to * {@code /accounts/{id}} where {@code {id}} stands for the method {@code id} parameter, deserialize the http response * to an {@code Account} instance and return it. If there is no such account (the GET request returned 404), * {@code null} must be returned. If some problem occurs (50x http response), a runtime exception must be thrown.</p> * * <p>For the integration testing of this component it would be great to have a way to start a stub http server * which would return predefined stub responses. This is where Jadler comes to help.</p> * * <p>Let's write such an integration test using <a href="http://junit.org" target="_blank">jUnit</a>:</p> * * <pre> * ... * import static net.jadler.Jadler.*; * ... * * public class AccountManagerImplTest { * * private static final String ID = "123"; * private static final String ACCOUNT_JSON = "{\"account\":{\"id\": \"123\"}}"; * * * {@literal @}Before * public void setUp() { * initJadler(); * } * * {@literal @}After * public void tearDown() { * closeJadler(); * } * * {@literal @}Test * public void getAccount() { * onRequest() * .havingMethodEqualTo("GET") * .havingPathEqualTo("/accounts/" + ID) * .respond() * .withBody(ACCOUNT_JSON) * .withStatus(200); * * final AccountManager am = new AccountManagerImpl("http", "localhost", port()); * * final Account account = ag.getAccount(ID); * * assertThat(account, is(notNullValue())); * assertThat(account.getId(), is(ID)); * } * } * </pre> * * <p>There are three main parts of this test. The <em>setUp</em> phase just initializes Jadler (which includes * starting a stub http server), while the <em>tearDown</em> phase just closes all resources. Nothing * interesting so far.</p> * * <p>All the magic happens in the test method. New http stub is defined, in the <em>THEN</em> part * the http stub server is instructed to return a specific http response * (200 http status with a body defined in the {@code ACCOUNT_JSON} constant) if the incoming http request * fits the given conditions defined in the <em>WHEN</em> part (must be a GET request to {@code /accounts/123}).</p> * * <p>In order to communicate with the http stub server instead of the real web service, the tested instance * must be configured to access {@code localhost} using the http protocol (https will be supported * in a latter version of Jadler) connecting to a port which can be retrieved using the {@link Jadler#port()} method.</p> * * <p>The rest of the test method is business as usual. The {@code getAccount(String)} is executed and some * assertions are evaluated.</p> * * <p>Now lets write two more test methods to test the 404 and 500 scenarios:</p> * * <pre> * {@literal @}Test * public void getAccountNotFound() { * onRequest() * .havingMethodEqualTo("GET") * .havingPathEqualTo("/accounts/" + ID) * .respond() * .withStatus(404); * * final AccountManager am = new AccountManagerImpl("http", "localhost", port()); * * Account account = am.getAccount(ID); * * assertThat(account, is(nullValue())); * } * * * {@literal @}Test(expected=RuntimeException.class) * public void getAccountError() { * onRequest() * .havingMethodEqualTo("GET") * .havingPathEqualTo("/accounts/" + ID) * .respond() * .withStatus(500); * * final AccountManager am = new AccountManagerImpl("http", "localhost", port()); * * am.getAccount(ID); * } * </pre> * * <p>The first test method checks the {@code getAccount(String)} method returns {@code null} if 404 is returned * from the server. The second one tests a runtime exception is thrown upon 500 http response.</p> * * * <h3>Multiple responses definition</h3> * <p>Sometimes you need to define more subsequent messages in your testing scenario. Let's test here * your code can recover from an unexpected 500 response and retry the POST receiving 201 this time:</p> * * <pre> * onRequest() * .havingPathEqualTo("/accounts") * .havingMethodEqualTo("POST") * .respond() * .withStatus(500) * .thenRespond() * .withStatus(201); * </pre> * * <p>The stub server will return a stub http response with 500 response status for the first request * which suits the stub rule. A stub response with 201 response status will be returned for the second request * (and all subsequent requests as well).</p> * * <h3>More suitable stub rules</h3> * * <p>It's not uncommon that more stub rules can be applied (the incoming request fits more than one <em>WHEN</em> * part). Let's have the following example: </p> * * <pre> * onRequest() * .havingPathEqualTo("/accounts") * .respond() * .withStatus(201); * * onRequest() * .havingMethodEqualTo("POST") * .respond() * .withStatus(202); * </pre> * * <p>If a POST http request was sent to {@code /accounts} both rules would be applicable. However, the latter stub * gets priority over the former one. In this example, an http response with {@code 202} status code would be * returned.</p> * * <h3>Advanced http stubbing</h3> * <h4 id="stubbing">The <em>WHEN</em> part</h4> * <p>So far two {@code having*} methods have been introduced, * {@link RequestStubbing#havingMethodEqualTo(java.lang.String)} to check the http method equality and * {@link RequestStubbing#havingPathEqualTo(java.lang.String)} to check the path equality. But there's more!</p> * * <p>You can use {@link RequestStubbing#havingBodyEqualTo(java.lang.String)} and * {@link RequestStubbing#havingRawBodyEqualTo(byte[])}} to check the request body equality * (either as a string or as an array of bytes).</p> * * <p>Feel free to to use {@link RequestStubbing#havingQueryStringEqualTo(java.lang.String)} * to test the query string value.</p> * * <p>And finally don't hesitate to use {@link RequestStubbing#havingParameterEqualTo(java.lang.String, java.lang.String)} * or {@link RequestStubbing#havingHeaderEqualTo(java.lang.String, java.lang.String)} * for a check whether there is an http parameter / header in the incoming request with a given value. * If an existence check is sufficient you can use {@link RequestStubbing#havingParameter(java.lang.String)}, * {@link RequestStubbing#havingParameters(java.lang.String[])} or * {@link RequestStubbing#havingHeader(java.lang.String)}, {@link RequestStubbing#havingHeaders(java.lang.String[])} * instead.</p> * * <p>So let's write some advanced http stub here: </p> * * <pre> * onRequest() * .havingMethodEqualTo("POST") * .havingPathEqualTo("/accounts") * .havingBodyEqualTo("{\"account\":{}}") * .havingHeaderEqualTo("Content-Type", "application/json") * .havingParameterEqualTo("force", "1") * .respond() * .withStatus(201); * </pre> * * <p>The 201 stub response will be returned if the incoming request was a {@code POST} request to {@code /accounts} * with the specified body, {@code application/json} content type header and a {@code force} http parameter set to * {@code 1}.</p> * * <h4>The <em>THEN</em> part</h4> * <p>There are much more options than just setting the http response status using the * {@link net.jadler.stubbing.ResponseStubbing#withStatus(int)} in the <em>THEN</em> part of an http stub.</p> * * <p>You will probably have to define the stub response body as a string very often. That's what the * {@link net.jadler.stubbing.ResponseStubbing#withBody(java.lang.String)} and * {@link net.jadler.stubbing.ResponseStubbing#withBody(java.io.Reader)} methods are for. These * are very often used in conjunction of * {@link net.jadler.stubbing.ResponseStubbing#withEncoding(java.nio.charset.Charset)} to define the * encoding of the response body</p> * * <p>If you'd like to define the stub response body binary, feel free to use either * {@link net.jadler.stubbing.ResponseStubbing#withBody(byte[])} or * {@link net.jadler.stubbing.ResponseStubbing#withBody(java.io.InputStream)}.</p> * * <p>Setting a stub response header is another common http stubbing use case. Just call * {@link net.jadler.stubbing.ResponseStubbing#withHeader(java.lang.String, java.lang.String)} to * set such header. For setting the {@code Content-Type} header you can use specially tailored * {@link net.jadler.stubbing.ResponseStubbing#withContentType(java.lang.String)} method.</p> * * <p>And finally sometimes you would like to simulate a network latency. To do so just call the * {@link net.jadler.stubbing.ResponseStubbing#withDelay(long, java.util.concurrent.TimeUnit)} method. * The stub response will be returned after the specified amount of time or later.</p> * * <p>Let's define the <em>THEN</em> part precisely:</p> * * <pre> * onRequest() * .havingMethodEqualTo("POST") * .havingPathEqualTo("/accounts") * .havingBodyEqualTo("{\"account\":{}}") * .havingHeaderEqualTo("Content-Type", "application/json") * .havingParameterEqualTo("force", "1") * .respond() * .withDelay(2, SECONDS) * .withStatus(201) * .withBody("{\"account\":{\"id\" : 1}}") * .withEncoding(Charset.forName("UTF-8")) * .withContentType("application/json; charset=UTF-8") * .withHeader("Location", "/accounts/1"); * </pre> * * <p>If the incoming http request fulfills the <em>WHEN</em> part, a stub response will be returned after at least * 2 seconds. The response will have 201 status code, defined json body encoded using UTF-8 and both * {@code Content-Type} and {@code Location} headers set to proper values.</p> * * * <h3> Even more advanced http stubbing</h3> * * <h4>Fine-tuning the <em>WHEN</em> part using predicates</h4> * <p>So far we have been using the equality check to define the <em>WHEN</em> part. However it's quite useful * to be able to use other predicates (<em>non empty string</em>, <em>contains string</em>, ...) then just * the request value equality.</p> * * <p>Jadler uses <a href="http://hamcrest.org" target="_blank">Hamcrest</a> as a predicates library. Not only * it provides many already implemented predicates (called matchers) but also a simple way to implement * your own ones if necessary. More on Hamcrest usage to be found in this * <a href="http://code.google.com/p/hamcrest/wiki/Tutorial" target="_blank">tutorial</a>.</p> * * <p>So let's write the following stub: if an incoming request has a non-empty body and the request method * is not PUT and the path value starts with <em>/accounts</em> then return an empty response * with the 200 http status:</p> * * <pre> * onRequest() * .havingBody(not(isEmptyOrNullString())) * .havingPath(startsWith("/accounts")) * .havingMethod(not(equalToIgnoringCase("PUT"))) * .respond() * .withStatus(200); * </pre> * * <p>You can use following <em>having*</em> methods for defining the <em>WHEN</em> part * using a Hamcrest string matcher: </p> * * <ul> * <li>{@link RequestStubbing#havingBody(org.hamcrest.Matcher)}</li> * <li>{@link RequestStubbing#havingMethod(org.hamcrest.Matcher)}</li> * <li>{@link RequestStubbing#havingQueryString(org.hamcrest.Matcher)}</li> * <li>{@link RequestStubbing#havingPath(org.hamcrest.Matcher)}</li> * </ul> * * <p>For adding predicates about request parameters and headers use * {@link RequestStubbing#havingHeader(java.lang.String, org.hamcrest.Matcher)} and * {@link RequestStubbing#havingParameter(java.lang.String, org.hamcrest.Matcher)} methods. Since a request header or * parameter can have more than one value, these methods accept a list of strings predicates.</p> * * <p>All introduced methods allow user to add a predicate about a part of an http request (body, method, ...). * If you need to add a predicate about the whole request object (of type {@link Request}), * you can use the {@link RequestStubbing#that(org.hamcrest.Matcher)} method: </p> * * <pre> * //meetsCriteria() is some factory method returning a Matcher<Request> instance * * onRequest() * .that(meetsCriteria()) * .respond() * .withStatus(204); * </pre> * * * <h4>Fine-tuning the <em>THEN</em> part using defaults</h4> * * <p>It's pretty common many <em>THEN</em> parts share similar settings. Let's have two or more stubs returning * an http response with 200 http status. Instead of calling {@link ResponseStubbing#withStatus(int)} during * every stubbing Jadler can be instructed to use 200 as a default http status: </p> * * <pre> * {@literal @}Before * public void setUp() { * initJadler() * .withDefaultResponseStatus(200); * } * </pre> * * <p>This particular test setup configures Jadler to return http stub responses with 200 http * status by default. This default can always be overwritten by calling the {@link ResponseStubbing#withStatus(int)} * method in the particular stubbing.</p> * * <p>The following example demonstrates all response defaults options: </p> * * <pre> * {@literal @}Before * public void setUp() { * initJadler() * .withDefaultResponseStatus(202) * .withDefaultResponseContentType("text/plain") * .withDefaultResponseEncoding(Charset.forName("ISO-8859-1")) * .withDefaultResponseHeader("X-DEFAULT-HEADER", "default_value"); * } * </pre> * * <p>If not redefined in the particular stubbing, every stub response will have 202 http status, {@code Content-Type} * header set to {@code text/plain}, response body encoded using {@code ISO-8859-1} and a header named * {@code X-DEFAULT-HEADER} set to {@code default_value}.</p> * * <p>And finally if no default nor stubbing-specific status code is defined 200 will be used. And if no default * nor stubbing-specific response body encoding is defined, {@code UTF-8} will be used by default.</p> * * * <h3>Generating a stub response dynamically</h3> * * <p>In some integration testing scenarios it's necessary to generate a stub http response dynamically. This * is a case where the {@code with*} methods aren't sufficient. However Jadler comes to help here with with * the {@link net.jadler.stubbing.Responder} interface which allows to define the stub response dynamically * according to the received request: </p> * * <pre> * onRequest() * .havingMethodEqualTo("POST") * .havingPathEqualTo("/accounts") * .respondUsing(new Responder() { * * private final AtomicInteger cnt = new AtomicInteger(1); * * {@literal @}Override * public StubResponse nextResponse(final Request request) { * final int current = cnt.getAndIncrement(); * final String headerValue = request.getHeaders().getValue("x-custom-request-header"); * return StubResponse.builder() * .status(current % 2 == 0 ? 200 : 500) * .header("x-custom-response-header", headerValue) * .build(); * } * }); * </pre> * * <p>The intention to define the stub response dynamically is expressed by using * {@link net.jadler.stubbing.RequestStubbing#respondUsing(net.jadler.stubbing.Responder)}. This method takes * a {@link net.jadler.stubbing.Responder} implementation as a parameter, Jadler subsequently uses the * {@link net.jadler.stubbing.Responder#nextResponse(net.jadler.Request)} method to generate stub responses for * all requests fitting the given <em>WHEN</em> part.</p> * * <p>In the previous example the http status of a stub response is {@code 200} for even requests and {@code 500} * for odd requests. And the value of the {@code x-custom-request-header} request header is used as * a response header.</p> * * <p>As you can see in the example this {@link net.jadler.stubbing.Responder} implementation is thread-safe * (by using {@link java.util.concurrent.atomic.AtomicInteger} here). This is important for tests of parallel nature * (more than one client can send requests fitting the <em>WHEN</em> part in parallel). Of course if requests are sent * in a serial way (which is the most common case) there is no need for the thread-safety of the implementation.</p> * * <p>Please note this dynamic way of defining stub responses should be used as rarely as possible as * it very often signalizes a problem either with test granularity or somewhere in the tested code. However there * could be very specific testing scenarios where this functionality might be handy.</p> * * * <h3>Request Receipt Verification</h3> * * <p>While the Jadler library is invaluable in supporting your test scenarios by providing a stub http server, * it has even more to offer.</p> * * <p>Very often it's necessary not only to provide a stub http response but also to verify that a specific * http request was received during a test execution. Let's add a removal operation to the already introduced * {@code AccountManager} interface: </p> * * <pre> * public interface AccountManager { * Account getAccount(String id); * * void deleteAccount(String id); * } * </pre> * * <p>The {@code deleteAccount} operation is supposed to delete an account by sending a {@code DELETE} http request * to {@code /accounts/{id}} where {@code {id}} stands for the operation {@code id} parameter. If the response status is * 204 the removal is considered successful and the execution is finished successfully. Let's write an integration * test for this scenario:</p> * * <pre> * ... * import static net.jadler.Jadler.*; * ... * * public class AccountManagerImplTest { * * private static final String ID = "123"; * * {@literal @}Before * public void setUp() { * initJadler(); * } * * {@literal @}After * public void tearDown() { * closeJadler(); * } * * {@literal @}Test * public void deleteAccount() { * onRequest() * .havingMethodEqualTo("DELETE") * .havingPathEqualTo("/accounts/" + ID) * .respond() * .withStatus(204); * * final AccountManager am = new AccountManagerImpl("http", "localhost", port()); * * final Account account = am.deleteAccount(ID); * * verifyThatRequest() * .havingMethodEqualTo("DELETE") * .havingPathEqualTo("/accounts/" + ID) .receivedOnce(); * } * } * </pre> * * <p>The first part of this test is business as usual. An http stub is created and the tested method * {@code deleteAccount} is invoked. However in this test case we would like to test whether the {@code DELETE} http * request was really sent during the execution of the method.</p> * * <p>This is where Jadler comes again to help. Calling {@link #verifyThatRequest()} signalizes an intention to * verify a number of requests received so far meeting the given criteria. The criteria is defined using exactly * the same {@code having*} methods which has been already described in the <a href="#stubbing">stubbing section</a> * (the methods are defined in the {@link RequestMatching} interface).</p> * * <p>The request definition must be followed by calling one of the {@code received*} methods. The already * introduced {@link Verifying#receivedOnce()} method verifies there has been received exactly one request meeting * the given criteria so far. If the verification fails a {@link VerificationException} instance is thrown and * the exact reason is logged on the {@code INFO} level.</p> * * <p>There are three more verification methods. {@link Verifying#receivedNever()} verifies there has not been * received any request meeting the given criteria so far. {@link Verifying#receivedTimes(int)} allows to define * the exact number of requests meeting the given criteria. And finally * {@link Verifying#receivedTimes(org.hamcrest.Matcher)} allows to apply a Hamcrest matcher on the number of * requests meeting the given criteria. The following example shows how to verify there have been at most * 3 DELETE requests sent so far:</p> * * <pre> * verifyThatRequest() * .havingMethodEqualTo("DELETE") * .receivedTimes(lessThan(4)); * </pre> * * <p>This verification feature is implemented by recording all incoming http requests (including their bodies). In * some very specific corner cases this implementation can cause troubles. For example imagine a long running * performance test using Jadler for stubbing some remote http service. Since such a test can issue thousands * or even millions of requests the memory consumption probably would affect the test results (either * by a performance slowdown or even crashes). In this specific scenarios you should consider disabling * the incoming requests recording:</p> * * <pre> * {@literal @}Before * public void setUp() { * initJadler() * .withRequestsRecordingDisabled(); * } * </pre> * * <p>Once the request recording has been disabled, calling {@link net.jadler.mocking.Mocker#verifyThatRequest()} * will result in {@link IllegalStateException}.</p> * * <p>Please note you should ignore this option almost every time you use Jadler unless you are really * convinced about it. Because premature optimization is the root of all evil, you know.</p> * * <h3>Jadler Lifecycle</h3> * * <p>As already demonstrated, the standard Jadler lifecycle consists of the following steps: </p> * * <ol> * <li>starting Jadler including the underlying http server (by calling one of the {@code initJadler*} methods of the * {@link Jadler} facade) in the <em>setUp</em> phase of a test</li> * * <li>stubbing using the {@link Jadler#onRequest()} method at the beginning of the test method</li> * * <li>calling the code to be tested</li> * * <li>doing some verification using {@link Jadler#verifyThatRequest()} if necessary</li> * * <li>closing Jadler including the underlying http server (by calling the {@link Jadler#closeJadler()}) method * in the <em>tearDown</em> phase of a test</li> * </ol> * * <p>These steps are then repeated for every test in a test suite. This lifecycle is fully covered by the static * {@link Jadler} facade which encapsulates and manages an instance of the core {@link JadlerMocker} component.</p> * * <h4>Creating mocker instances manually</h4> * * <p>There are few specific scenarios when creating {@link JadlerMocker} instances manually (instead of using the * {@link Jadler} facade) can be handy. Some specific integration tests may require starting more than just one mocker * on different ports (simulating requesting multiple different http servers). If this is the case, * all the mocker instances have to be created manually (since the facade encapsulates just one mocker instance).</p> * * <p>To achieve this each mocker must be created and disposed before and after every test: </p> * * <pre> * public class ManualTest { * * private JadlerMocker mocker; * private int port; * * {@literal @}Before * public void setUp() { * mocker = new JadlerMocker(new JettyStubHttpServer()); * mocker.start(); * port = getStubHttpServerPort(); * } * * {@literal @}After * public void tearDown() { * mocker.close(); * } * * {@literal @}Test * public void testSomething() { * mocker.onRequest().respond().withStatus(404); * * //call the code to be tested here * * mocker.verifyThatRequest().receivedOnce(); * } * } * </pre> * * * <h4>Simplified Jadler Lifecycle Management</h4> * * <p>In all previous examples the jUnit {@literal @}Before and {@literal @}After sections were used to manage * the Jadler lifecycle. If jUnit 4.11 (or newer) is on the classpath a simple Jadler * <a href="https://github.com/junit-team/junit/wiki/Rules">rule</a> {@link net.jadler.junit.rule.JadlerRule} * can be used instead:</p> * * <pre> * public class AccountManagerImplTest { * * {@literal @}Rule * public JadlerRule jadlerRule = new JadlerRule(); * * ... * } * </pre> * * <p>This piece of code starts Jadler on a random port at the beginning of each test and closes it at the end. * A specific port can be defined as well: {@code new JadlerRule(12345);}. Please note this is exactly the same as * calling {@link Jadler#initJadler()} and {@link Jadler#closeJadler()} in the {@code setUp} and {@code tearDown} * methods.</p> * * <p>To use this rule the {@code jadler-junit} artifact must be on the classpath.</p> */ public class Jadler { //since jUnit might execute tests in a thread other than the thread executing the setup and teardown methods //when specific conditions are met (a timeout value is specified for the given //test, see http://junit.org/apidocs/org/junit/Test.html for details), the thread local container //is inheritable so the content is copied automatically to the child thread private static final ThreadLocal<JadlerMocker> jadlerMockerContainer = new InheritableThreadLocal<JadlerMocker>(); private static final String JETTY_SERVER_CLASS = "net.jadler.stubbing.server.jetty.JettyStubHttpServer"; private Jadler() { //gtfo } /** * <p>Initializes Jadler and starts a default stub server {@link net.jadler.stubbing.server.jetty.JettyStubHttpServer} * serving the http protocol listening on any free port. The port number can be retrieved using {@link #port()}.</p> * * <p>This should be preferably called in the {@code setUp} method of the test suite.</p> * * @return {@link OngoingConfiguration} instance for additional configuration and tweaking * (use its {@code with*} methods) */ public static OngoingConfiguration initJadler() { return initInternal(new JadlerMocker(getJettyServer())); } /** * <p>Initializes Jadler and starts a default stub server {@link net.jadler.stubbing.server.jetty.JettyStubHttpServer} * serving the http protocol listening on the given port.</p> * * <p>This should be preferably called in the {@code setUp} method of the test suite.</p> * * @param port port the stub server will be listening on * @return {@link OngoingConfiguration} instance for additional configuration and tweaking * (use its {@code with*} methods) */ public static OngoingConfiguration initJadlerListeningOn(final int port) { return initInternal(new JadlerMocker(getJettyServer(port))); } /** * <p>Initializes Jadler and starts the given {@link StubHttpServer}.</p> * * <p>This should be preferably called in the {@code setUp} method of the test suite</p> * * @param server stub http server instance * @return {@link OngoingConfiguration} instance for additional configuration and tweaking * (use its {@code with*} methods) */ public static OngoingConfiguration initJadlerUsing(final StubHttpServer server) { return initInternal(new JadlerMocker(server)); } /** * <p>Stops the underlying {@link StubHttpServer} and closes Jadler.</p> * * <p>This should be preferably called in the {@code tearDown} method of a test suite.</p> */ public static void closeJadler() { final StubHttpServerManager serverManager = jadlerMockerContainer.get(); if (serverManager != null && serverManager.isStarted()) { serverManager.close(); } jadlerMockerContainer.set(null); } /** * <p>Resets Jadler by clearing all previously created stubs as well as stored received requests.</p> * * <p>While the standard Jadler lifecycle consists of initializing Jadler and starting the * underlying stub server (using {@link #initJadler()}) in the <em>setUp</em> section of a test and stopping * the server (using {@link #closeJadler()}) in the <em>tearDown</em> section, in some specific scenarios * it could be useful to reuse initialized Jadler in all tests instead.</p> * * <p>Here's an example code using jUnit which demonstrates usage of this method in a test lifecycle:</p> * * <pre> * public class JadlerResetIntegrationTest { * * {@literal @}BeforeClass * public static void beforeTests() { * initJadler(); * } * * {@literal @}AfterClass * public static void afterTests() { * closeJadler(); * } * * {@literal @}After * public void reset() { * resetJadler(); * } * * {@literal @}Test * public void test1() { * mocker.onRequest().respond().withStatus(201); * * //do an http request here, 201 should be returned from the stub server * * verifyThatRequest().receivedOnce(); * } * * {@literal @}Test * public void test2() { * mocker.onRequest().respond().withStatus(400); * * //do an http request here, 400 should be returned from the stub server * * verifyThatRequest().receivedOnce(); * } * } * </pre> * * <p>Please note the standard lifecycle should be always preferred since it ensures a full independence * of all tests in a suite. However performance issues may appear theoretically while starting and stopping * the server as a part of each test. If this is your case the alternative lifecycle might be handy.</p> * * <p>Also note that calling this method in a test body <strong>always</strong> signalizes a poorly written test * with a problem with the granularity. In this case consider writing more fine grained tests instead of using this * method.</p> * * @see JadlerMocker#reset() */ public static void resetJadler() { final JadlerMocker mocker = jadlerMockerContainer.get(); if (mocker != null) { mocker.reset(); } } /** * Use this method to retrieve the port the underlying http stub server is listening on * @return the port the underlying http stub server is listening on * @throws IllegalStateException if Jadler has not been initialized yet */ public static int port() { checkInitialized(); return jadlerMockerContainer.get().getStubHttpServerPort(); } /** * Starts new http stubbing (defining new <i>WHEN</i>-<i>THEN</i> rule). * @return stubbing object for ongoing stubbing */ public static RequestStubbing onRequest() { checkInitialized(); return jadlerMockerContainer.get().onRequest(); } /** * Starts new verification (checking that an http request with given properties was or was not received) * @return verifying object for ongoing verifying */ public static Verifying verifyThatRequest() { checkInitialized(); return jadlerMockerContainer.get().verifyThatRequest(); } private static void checkInitialized() { if (jadlerMockerContainer.get() == null) { throw new IllegalStateException("Jadler has not been initialized yet."); } } private static OngoingConfiguration initInternal(final JadlerMocker jadlerMocker) { if (jadlerMockerContainer.get() != null) { throw new IllegalStateException("Jadler seems to have been initialized already."); } jadlerMockerContainer.set(jadlerMocker); jadlerMocker.start(); return OngoingConfiguration.INSTANCE; } private static StubHttpServer getJettyServer() { final Class<?> clazz = getJettyStubHttpServerClass(); try { return (StubHttpServer) clazz.newInstance(); } catch (final Exception e) { throw new JadlerException("Cannot instantiate default Jetty stub server", e); } } private static StubHttpServer getJettyServer(final int port) { final Class<?> clazz = getJettyStubHttpServerClass(); try { return (StubHttpServer) clazz.getConstructor(int.class).newInstance(port); } catch (final Exception e) { throw new JadlerException("Cannot instantiate default Jetty stub server with the given port", e); } } private static Class<?> getJettyStubHttpServerClass() { try { return Class.forName(JETTY_SERVER_CLASS); } catch (final ClassNotFoundException e) { throw new JadlerException("Class " + JETTY_SERVER_CLASS + " cannot be found. " + "Either add jadler-jetty to your classpath or use the initJadlerUsing method to specify the " + "stub server explicitly.", e); } } /** * This class serves as a DSL support for additional Jadler configuration. */ public static class OngoingConfiguration implements JadlerConfiguration { private static final OngoingConfiguration INSTANCE = new OngoingConfiguration(); private OngoingConfiguration() { //private constructor, instances of this class should never be created directly } /** * @return this ongoing configuration * @deprecated added just for backward compatibility reasons, does nothing but returning this ongoing * configuration instance. Use the configuration methods of this instance directly instead. */ @Deprecated public OngoingConfiguration that() { return this; } /** * @deprecated use {@link #withDefaultResponseStatus(int)} instead * * Sets the default http response status. This value will be used for all stub responses with no * specific http status defined. (see {@link ResponseStubbing#withStatus(int)}) * @param defaultStatus default http response status * @return this ongoing configuration */ @Deprecated public OngoingConfiguration respondsWithDefaultStatus(final int defaultStatus) { return this.withDefaultResponseStatus(defaultStatus); } /** * {@inheritDoc} */ @Override public OngoingConfiguration withDefaultResponseStatus(final int defaultStatus) { jadlerMockerContainer.get().setDefaultStatus(defaultStatus); return this; } /** * @deprecated use {@link #withDefaultResponseHeader(java.lang.String, java.lang.String)} instead * * Defines a response header that will be sent in every http stub response. * Can be called repeatedly to define more headers. * @param name name of the header * @param value header value * @return this ongoing configuration */ @Deprecated public OngoingConfiguration respondsWithDefaultHeader(final String name, final String value) { return this.withDefaultResponseHeader(name, value); } /** * {@inheritDoc} */ @Override public OngoingConfiguration withDefaultResponseHeader(final String name, final String value) { jadlerMockerContainer.get().addDefaultHeader(name, value); return this; } /** * @deprecated use {@link #withDefaultResponseEncoding(java.nio.charset.Charset)} instead * * Defines a default encoding of every stub http response. This value will be used for all stub responses * with no specific encoding defined. (see {@link ResponseStubbing#withEncoding(java.nio.charset.Charset)}) * @param defaultEncoding default stub response encoding * @return this ongoing configuration */ @Deprecated public OngoingConfiguration respondsWithDefaultEncoding(final Charset defaultEncoding) { return this.withDefaultResponseEncoding(defaultEncoding); } /** * {@inheritDoc} */ @Override public OngoingConfiguration withDefaultResponseEncoding(final Charset defaultEncoding) { jadlerMockerContainer.get().setDefaultEncoding(defaultEncoding); return this; } /** * @deprecated use {@link #withRequestsRecordingDisabled()} instead * * <p>Disables incoming http requests recording.</p> * * <p>Jadler mocking (verification) capabilities are implemented by storing all incoming requests (including their * bodies). This could cause troubles in some very specific testing scenarios, for further explanation jump * straight to {@link JadlerMocker#setRecordRequests(boolean)}.</p> * * <p>Please note this method should be used very rarely and definitely should not be treated as a default.</p> * * @see JadlerMocker#setRecordRequests(boolean) * @return this ongoing configuration */ @Deprecated public OngoingConfiguration skipsRequestsRecording() { return this.withRequestsRecordingDisabled(); } /** * {@inheritDoc} */ @Override public OngoingConfiguration withRequestsRecordingDisabled() { jadlerMockerContainer.get().setRecordRequests(false); return this; } /** * @deprecated use {@link #withDefaultResponseContentType(java.lang.String)} instead * * Defines a default content type of every stub http response. This value will be used for all stub responses * with no specific content type defined. (see {@link ResponseStubbing#withContentType(java.lang.String)}) * @param defaultContentType default {@code Content-Type} header of every http stub response * @return this ongoing configuration */ @Deprecated public OngoingConfiguration respondsWithDefaultContentType(final String defaultContentType) { return this.withDefaultResponseContentType(defaultContentType); } /** * {@inheritDoc} */ @Override public OngoingConfiguration withDefaultResponseContentType(final String defaultContentType) { return this.withDefaultResponseHeader("Content-Type", defaultContentType); } } }