Package net.jadler

Class Jadler


  • public class Jadler
    extends Object

    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.

    Jadler Usage Basics

    Let's have a simple component with one operation:

     public interface AccountManager {
       Account getAccount(String id);
     }
     

    An implementation of the getAccount operation is supposed to send a GET http request to /accounts/{id} where {id} stands for the method id parameter, deserialize the http response to an Account instance and return it. If there is no such account (the GET request returned 404), null must be returned. If some problem occurs (50x http response), a runtime exception must be thrown.

    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.

    Let's write such an integration test using jUnit:

     ...
     import static net.jadler.Jadler.*;
     ...
    
     public class AccountManagerImplTest {
    
         private static final String ID = "123";
         private static final String ACCOUNT_JSON = "{\"account\":{\"id\": \"123\"}}";
    
    
         @Before
         public void setUp() {
             initJadler();
         }
    
         @After
         public void tearDown() {
             closeJadler();
         }
    
         @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));
         }
     }
     

    There are three main parts of this test. The setUp phase just initializes Jadler (which includes starting a stub http server), while the tearDown phase just closes all resources. Nothing interesting so far.

    All the magic happens in the test method. New http stub is defined, in the THEN part the http stub server is instructed to return a specific http response (200 http status with a body defined in the ACCOUNT_JSON constant) if the incoming http request fits the given conditions defined in the WHEN part (must be a GET request to /accounts/123).

    In order to communicate with the http stub server instead of the real web service, the tested instance must be configured to access 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 port() method.

    The rest of the test method is business as usual. The getAccount(String) is executed and some assertions are evaluated.

    Now lets write two more test methods to test the 404 and 500 scenarios:

     @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()));
     }
    
    
     @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);
     }
     

    The first test method checks the getAccount(String) method returns null if 404 is returned from the server. The second one tests a runtime exception is thrown upon 500 http response.

    Multiple responses definition

    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:

     onRequest()
         .havingPathEqualTo("/accounts")
         .havingMethodEqualTo("POST")
     .respond()
         .withStatus(500)
     .thenRespond()
         .withStatus(201);
     

    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).

    More suitable stub rules

    It's not uncommon that more stub rules can be applied (the incoming request fits more than one WHEN part). Let's have the following example:

     onRequest()
         .havingPathEqualTo("/accounts")
     .respond()
         .withStatus(201);
    
     onRequest()
         .havingMethodEqualTo("POST")
     .respond()
         .withStatus(202);
     

    If a POST http request was sent to /accounts both rules would be applicable. However, the latter stub gets priority over the former one. In this example, an http response with 202 status code would be returned.

    Advanced http stubbing

    The WHEN part

    So far two having* methods have been introduced, RequestMatching.havingMethodEqualTo(java.lang.String) to check the http method equality and RequestMatching.havingPathEqualTo(java.lang.String) to check the path equality. But there's more!

    You can use RequestMatching.havingBodyEqualTo(java.lang.String) and RequestMatching.havingRawBodyEqualTo(byte[])} to check the request body equality (either as a string or as an array of bytes).

    Feel free to to use RequestMatching.havingQueryStringEqualTo(java.lang.String) to test the query string value.

    And finally don't hesitate to use RequestMatching.havingParameterEqualTo(java.lang.String, java.lang.String) or RequestMatching.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 RequestMatching.havingParameter(java.lang.String), RequestMatching.havingParameters(java.lang.String[]) or RequestMatching.havingHeader(java.lang.String), RequestMatching.havingHeaders(java.lang.String[]) instead.

    So let's write some advanced http stub here:

     onRequest()
         .havingMethodEqualTo("POST")
         .havingPathEqualTo("/accounts")
         .havingBodyEqualTo("{\"account\":{}}")
         .havingHeaderEqualTo("Content-Type", "application/json")
         .havingParameterEqualTo("force", "1")
     .respond()
         .withStatus(201);
     

    The 201 stub response will be returned if the incoming request was a POST request to /accounts with the specified body, application/json content type header and a force http parameter set to 1.

    The THEN part

    There are much more options than just setting the http response status using the ResponseStubbing.withStatus(int) in the THEN part of an http stub.

    You will probably have to define the stub response body as a string very often. That's what the ResponseStubbing.withBody(java.lang.String) and ResponseStubbing.withBody(java.io.Reader) methods are for. These are very often used in conjunction of ResponseStubbing.withEncoding(java.nio.charset.Charset) to define the encoding of the response body

    If you'd like to define the stub response body binary, feel free to use either ResponseStubbing.withBody(byte[]) or ResponseStubbing.withBody(java.io.InputStream).

    Setting a stub response header is another common http stubbing use case. Just call ResponseStubbing.withHeader(java.lang.String, java.lang.String) to set such header. For setting the Content-Type header you can use specially tailored ResponseStubbing.withContentType(java.lang.String) method.

    And finally sometimes you would like to simulate a network latency. To do so just call the ResponseStubbing.withDelay(long, java.util.concurrent.TimeUnit) method. The stub response will be returned after the specified amount of time or later.

    Let's define the THEN part precisely:

     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");
     

    If the incoming http request fulfills the WHEN 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 Content-Type and Location headers set to proper values.

    Even more advanced http stubbing

    Fine-tuning the WHEN part using predicates

    So far we have been using the equality check to define the WHEN part. However it's quite useful to be able to use other predicates (non empty string, contains string, ...) then just the request value equality.

    Jadler uses Hamcrest 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 tutorial.

    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 /accounts then return an empty response with the 200 http status:

     onRequest()
         .havingBody(not(isEmptyOrNullString()))
         .havingPath(startsWith("/accounts"))
         .havingMethod(not(equalToIgnoringCase("PUT")))
     .respond()
         .withStatus(200);
     

    You can use following having* methods for defining the WHEN part using a Hamcrest string matcher:

    For adding predicates about request parameters and headers use RequestMatching.havingHeader(java.lang.String, org.hamcrest.Matcher) and RequestMatching.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.

    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 Request), you can use the RequestMatching.that(org.hamcrest.Matcher) method:

       //meetsCriteria() is some factory method returning a Matcher<Request> instance
    
     onRequest()
         .that(meetsCriteria())
     .respond()
         .withStatus(204);
     

    Fine-tuning the THEN part using defaults

    It's pretty common many THEN parts share similar settings. Let's have two or more stubs returning an http response with 200 http status. Instead of calling ResponseStubbing.withStatus(int) during every stubbing Jadler can be instructed to use 200 as a default http status:

     @Before
     public void setUp() {
         initJadler()
             .withDefaultResponseStatus(200);
     }
     

    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 ResponseStubbing.withStatus(int) method in the particular stubbing.

    The following example demonstrates all response defaults options:

       @Before
       public void setUp() {
           initJadler()
               .withDefaultResponseStatus(202)
               .withDefaultResponseContentType("text/plain")
               .withDefaultResponseEncoding(Charset.forName("ISO-8859-1"))
               .withDefaultResponseHeader("X-DEFAULT-HEADER", "default_value");
       }
     

    If not redefined in the particular stubbing, every stub response will have 202 http status, Content-Type header set to text/plain, response body encoded using ISO-8859-1 and a header named X-DEFAULT-HEADER set to default_value.

    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, UTF-8 will be used by default.

    Generating a stub response dynamically

    In some integration testing scenarios it's necessary to generate a stub http response dynamically. This is a case where the with* methods aren't sufficient. However Jadler comes to help here with with the Responder interface which allows to define the stub response dynamically according to the received request:

     onRequest()
         .havingMethodEqualTo("POST")
         .havingPathEqualTo("/accounts")
         .respondUsing(new Responder() {
    
             private final AtomicInteger cnt = new AtomicInteger(1);
    
             @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();
             }
         });
     

    The intention to define the stub response dynamically is expressed by using RequestStubbing.respondUsing(net.jadler.stubbing.Responder). This method takes a Responder implementation as a parameter, Jadler subsequently uses the Responder.nextResponse(net.jadler.Request) method to generate stub responses for all requests fitting the given WHEN part.

    In the previous example the http status of a stub response is 200 for even requests and 500 for odd requests. And the value of the x-custom-request-header request header is used as a response header.

    As you can see in the example this Responder implementation is thread-safe (by using AtomicInteger here). This is important for tests of parallel nature (more than one client can send requests fitting the WHEN 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.

    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.

    Request Receipt Verification

    While the Jadler library is invaluable in supporting your test scenarios by providing a stub http server, it has even more to offer.

    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 AccountManager interface:

     public interface AccountManager {
       Account getAccount(String id);
    
       void deleteAccount(String id);
     }
     

    The deleteAccount operation is supposed to delete an account by sending a DELETE http request to /accounts/{id} where {id} stands for the operation 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:

     ...
     import static net.jadler.Jadler.*;
     ...
    
     public class AccountManagerImplTest {
    
         private static final String ID = "123";
    
         @Before
         public void setUp() {
             initJadler();
         }
    
         @After
             public void tearDown() {
             closeJadler();
         }
    
         @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();
         }
     }
     

    The first part of this test is business as usual. An http stub is created and the tested method deleteAccount is invoked. However in this test case we would like to test whether the DELETE http request was really sent during the execution of the method.

    This is where Jadler comes again to help. Calling 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 having* methods which has been already described in the stubbing section (the methods are defined in the RequestMatching interface).

    The request definition must be followed by calling one of the received* methods. The already introduced Verifying.receivedOnce() method verifies there has been received exactly one request meeting the given criteria so far. If the verification fails a VerificationException instance is thrown and the exact reason is logged on the INFO level.

    There are three more verification methods. Verifying.receivedNever() verifies there has not been received any request meeting the given criteria so far. Verifying.receivedTimes(int) allows to define the exact number of requests meeting the given criteria. And finally 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:

     verifyThatRequest()
         .havingMethodEqualTo("DELETE")
     .receivedTimes(lessThan(4));
     

    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:

     @Before
     public void setUp() {
         initJadler()
                 .withRequestsRecordingDisabled();
     }
     

    Once the request recording has been disabled, calling Mocker.verifyThatRequest() will result in IllegalStateException.

    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.

    Jadler Lifecycle

    As already demonstrated, the standard Jadler lifecycle consists of the following steps:

    1. starting Jadler including the underlying http server (by calling one of the initJadler* methods of the Jadler facade) in the setUp phase of a test
    2. stubbing using the onRequest() method at the beginning of the test method
    3. calling the code to be tested
    4. doing some verification using verifyThatRequest() if necessary
    5. closing Jadler including the underlying http server (by calling the closeJadler()) method in the tearDown phase of a test

    These steps are then repeated for every test in a test suite. This lifecycle is fully covered by the static Jadler facade which encapsulates and manages an instance of the core JadlerMocker component.

    Creating mocker instances manually

    There are few specific scenarios when creating JadlerMocker instances manually (instead of using the 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).

    To achieve this each mocker must be created and disposed before and after every test:

     public class ManualTest {
    
         private JadlerMocker mocker;
         private int port;
    
         @Before
         public void setUp() {
             mocker = new JadlerMocker(new JettyStubHttpServer());
             mocker.start();
             port = getStubHttpServerPort();
         }
    
         @After
         public void tearDown() {
             mocker.close();
         }
    
         @Test
         public void testSomething() {
             mocker.onRequest().respond().withStatus(404);
    
               //call the code to be tested here
    
             mocker.verifyThatRequest().receivedOnce();
         }
     }
     

    Simplified Jadler Lifecycle Management

    In all previous examples the jUnit @Before and @After sections were used to manage the Jadler lifecycle. If jUnit 4.11 (or newer) is on the classpath a simple Jadler rule net.jadler.junit.rule.JadlerRule can be used instead:

     public class AccountManagerImplTest {
    
         @Rule
         public JadlerRule jadlerRule = new JadlerRule();
    
         ...
     }
     

    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: new JadlerRule(12345);. Please note this is exactly the same as calling initJadler() and closeJadler() in the setUp and tearDown methods.

    To use this rule the jadler-junit artifact must be on the classpath.

    • Method Detail

      • initJadler

        public static Jadler.OngoingConfiguration initJadler()

        Initializes Jadler and starts a default stub server net.jadler.stubbing.server.jetty.JettyStubHttpServer serving the http protocol listening on any free port. The port number can be retrieved using port().

        This should be preferably called in the setUp method of the test suite.

        Returns:
        Jadler.OngoingConfiguration instance for additional configuration and tweaking (use its with* methods)
      • initJadlerListeningOn

        public static Jadler.OngoingConfiguration initJadlerListeningOn​(int port)

        Initializes Jadler and starts a default stub server net.jadler.stubbing.server.jetty.JettyStubHttpServer serving the http protocol listening on the given port.

        This should be preferably called in the setUp method of the test suite.

        Parameters:
        port - port the stub server will be listening on
        Returns:
        Jadler.OngoingConfiguration instance for additional configuration and tweaking (use its with* methods)
      • closeJadler

        public static void closeJadler()

        Stops the underlying StubHttpServer and closes Jadler.

        This should be preferably called in the tearDown method of a test suite.

      • resetJadler

        public static void resetJadler()

        Resets Jadler by clearing all previously created stubs as well as stored received requests.

        While the standard Jadler lifecycle consists of initializing Jadler and starting the underlying stub server (using initJadler()) in the setUp section of a test and stopping the server (using closeJadler()) in the tearDown section, in some specific scenarios it could be useful to reuse initialized Jadler in all tests instead.

        Here's an example code using jUnit which demonstrates usage of this method in a test lifecycle:

         public class JadlerResetIntegrationTest {
        
             @BeforeClass
             public static void beforeTests() {
                 initJadler();
             }
        
             @AfterClass
             public static void afterTests() {
                 closeJadler();
             }
        
             @After
             public void reset() {
                 resetJadler();
             }
        
             @Test
             public void test1() {
                 mocker.onRequest().respond().withStatus(201);
        
                 //do an http request here, 201 should be returned from the stub server
        
                 verifyThatRequest().receivedOnce();
             }
        
             @Test
             public void test2() {
                 mocker.onRequest().respond().withStatus(400);
        
                 //do an http request here, 400 should be returned from the stub server
        
                 verifyThatRequest().receivedOnce();
             }
         }
         

        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.

        Also note that calling this method in a test body always 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.

        See Also:
        JadlerMocker.reset()
      • port

        public static int port()
        Use this method to retrieve the port the underlying http stub server is listening on
        Returns:
        the port the underlying http stub server is listening on
        Throws:
        IllegalStateException - if Jadler has not been initialized yet
      • onRequest

        public static RequestStubbing onRequest()
        Starts new http stubbing (defining new WHEN-THEN rule).
        Returns:
        stubbing object for ongoing stubbing
      • verifyThatRequest

        public static Verifying verifyThatRequest()
        Starts new verification (checking that an http request with given properties was or was not received)
        Returns:
        verifying object for ongoing verifying