Permalink
50cb95f Jun 11, 2016
@mkotsur @thomasleveil
335 lines (244 sloc) 14.2 KB

Developer's guide

One test can be better then dozen lines of documentation, so there are tests in guide package which illustrate sections from this manual. Don't underestimate them :-)

# Motivation Let's imagine you have an application or library which uses a REST interface. At some point you would like to make sure that it works exactly as expected and mocking low-level HTTP client is not always the best option. That's where Restito comes to play: it gives you a DSL to test your application with mocked server just like you would do it with any mocking framework.

There is a nice example of the use case when Restito helps.

# Starting and stopping stub server
    @Before
    public void start() {
        server = new StubServer().run();
    }

    ...

    @After
    public void stop() {
        server.stop();
    }
## Specific vs random port

By default, StubServer.DEFAULT_PORT is used, but if it's busy, then next available will be taken.

    @Test
    public void shouldStartServerOnRandomPortWhenDefaultPortIsBusy() {
        StubServer server1 = new StubServer().run();
        StubServer server2 = new StubServer().run();
        assertTrue(server2.getPort() > server1.getPort());
    }

If you want to specify port explicitly, then you can do something like that:

    @Test
    public void shouldUseSpecificPort() {
        StubServer server = new StubServer(8888).run();
        assertEquals(8888, server.getPort());
    }

See SpecificVsRandomPortTest.

#Using HTTPS

When you need to use HTTPS, this is just one configuration call...

See UsingHttpsTest.

    server = new StubServer().secured().run();
#Junit integration

!! To use this you must have junit 4.10+ on your classpath. Restito doesn't contain it bundled to save you from the dependency nightmare. !!

When you use Junit and want to reduce boilerplate code which starts/stops server you can use @NeedsServer annotation and ServerDependencyRule to start/stop server in parent class only for cases that require it.

Check this test for more details.

# Stubbing server behavior. ## Stub conditions _In fact, Restito's stub server is not just a stub. It also behaves like a mock and spy object according to M. Fowler's terminology. However it's called just a StubServer._

Stubbing is a way to teach server to behave as you wish.

import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;
import static com.xebialabs.restito.semantics.Action.stringContent;
import static com.xebialabs.restito.semantics.Condition.*;
    ...
        whenHttp(server).
                match(get("/asd"), parameter("bar", "foo")).
                then(ok(), stringContent("GET asd with parameter bar=foo"));

In this example your stub will return mentioned string content when GET request with HTTP parameter bar=foo is done.

List of all available conditions can be checked in the javadoc for Condition

If you want to use a custom condition, it's also very easy:

import static com.xebialabs.restito.semantics.Condition.*;
import com.xebialabs.restito.semantics.Predicate;
import com.xebialabs.restito.semantics.Call;
    ...
        Predicate<Call> uriEndsWithA = new Predicate<Call>() {
            @Override
            public boolean apply(final Call input) {
                return input.getUri().endsWith("a");
            }
        };
        whenHttp(server).match(custom(uriEndsWithA)).then(ok());

Conditions are resolved in reverse order, which makes it easy to have some 'default' behavior.

        whenHttp(server).match(alwaysTrue()).then(status(HttpStatus.OK_200));
        whenHttp(server).match(get("/bad")).then(status(HttpStatus.BAD_REQUEST_400));

In this case, when request comes, it will be first tested against last attached condition (i.e. "/bad" URL), and if it doesn't match, will fall back to the first condition which is always true.

If no matching conditions found at all, restito will respond with HTTP status 404 Not Found.

See StubConditionsAndActionsTest.

## Stub actions

Action is a second component of Stubs. When condition is met, action will be performed on the response (like adding content, setting header, etc.)

import static com.xebialabs.restito.semantics.Action.*;
import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;
import static com.xebialabs.restito.semantics.Condition.endsWithUri;
import static com.xebialabs.restito.semantics.Action.stringContent;
    ...
        whenHttp(server).
                match(endsWithUri("/demo")).
                then(status(HttpStatus.OK_200), stringContent("Hello world!"));

This example will make your stub output "Hello world!" with http status 200 for all types of requests (POST, GET, PUT, ...) when URI ends with "/demo".

Full list of actions can be found in the appropriate javadoc.

See StubConditionsAndActionsTest.

## Stub actions

You can make you server to respond with basic access authentication.

whenHttp(server).match(basicAuth("admin", "secret")).then(status(HttpStatus.OK_200));
whenHttp(server).match(not(basicAuth("admin", "secret"))).then(unauthorized());

The first line makes sure that server responds with status 200 when client sends username admin and password secret, and the second line tells the server to respond with status code 401 and special WWW-Authenticate header in other case.

## Automatic content type

When you use action resourceContent(), Restito will look at file extension and if it it's one of the types below, appropriate Content-Type will be set:

  • .xml => application/xml
  • .json => application/json

See AutomaticContentTypeTest.

## Expected stubs

Makes sure that certain stubbed condition has been called some number of times, or the sequence has been completed. See ExpectedStubTest to learn how to do it.

## Sequenced stub actions

Sometimes you need to have different responses based on the sequence number of a request.

Giving a different response every time

    whenHttp(server).
            match(get("/demo")).
            then(sequence(
                compose(status(OK_200), stringContent("This is 1")),
                compose(status(OK_200), stringContent("This is 2"))
            ));

The first 2 GETs to /demo will return different strings, just as you would expect. All the following requests will be treated as if there was no stub for those: 404 response.

Extracting a shared action

     whenHttp(server).
             match(get("/demo")).
             then(status(OK_200)).
             withSequence(
                 composite(stringContent("This is 1")),
                 composite(stringContent("This is 2"))
             );

OK_200 will be applied to each GET to /demo. First 2 requests will also receive respective string contents. All the following requests will still get OK_200.

Explicitly defining an action for the overfull requests

     whenHttp(server).
             match(get("/demo")).
             then(status(OK_200)).
             withSequence(
                 compose(stringContent("This is 1")),
                 compose(stringContent("This is 2"))
             ).whenExceeded(
               status(NOT_ACCEPTABLE_406)
             );

Will result in:

GET /demo -> OK_200, "This is 1"
GET /demo -> OK_200, "This is 1"
GET /demo -> NOT_ACCEPTABLE_406

If there is no whenExceeded, the overfull requests will be treated as if there is no stub for those (i.e. 404).

See SequencedSubActionsTest.

Credits to @shamoh for this feature.

## Autodiscovery of stubs content

This is an experimental feature, api will be changed in next releases

When you use get(), put(), post() or delete() condition, Restito will try to find resource on the classpath according to the rules defined defined below in the same order:

  • GET asd/bsd/asd => resource: restito/get.asd.bsd.asd
  • GET asd/bsd/asd => resource: restito/get/asd/bsd/asd
  • GET asd/bsd/asd => resource: restito/asd.bsd.asd
  • GET asd/bsd/asd => resource: restito/asd/bsd/asd
  • GET asd/bsd/asd => resource: restito/get.asd.bsd.asd.xml
  • GET asd/bsd/asd => resource: restito/get/asd/bsd/asd.xml
  • GET asd/bsd/asd => resource: restito/asd.bsd.asd.xml
  • GET asd/bsd/asd => resource: restito/asd/bsd/asd.xml
  • GET asd/bsd/asd => resource: restito/get.asd.bsd.asd.json
  • GET asd/bsd/asd => resource: restito/get/asd/bsd/asd.json
  • GET asd/bsd/asd => resource: restito/asd.bsd.asd.json
  • GET asd/bsd/asd => resource: restito/asd/bsd/asd.json

See AutodiscoveryOfStubsContentTest to get some inspiration.

# Verifying calls to server ## Simple verifications

To verify that some call to the server has happened once, you may use following DSL:

    verifyHttp(server).once(
            method(Method.POST),
            uri("/demo"),
            parameter("foo", "bar")
    );

For verifications you use the same conditions as for stubbing and complete list of them can be found at Condition javadoc and custom conditions can easily be created.

See SimpleVerificationsTest.

## Limiting number of calls

You have more options to limit number of calls:

See LimitingNumberOfCallsTest.

## Sequenced verifications

You can easily chain verifications:

    verifyHttp(server).once(
            method(Method.GET),
            uri("/first")
    ).then().once(
            method(Method.GET),
            uri("/second")
    );

See SequencedVerificationsTest.

## Using like a standalone stub server

It is possible to use Restito as a standalone server (if you need to have a server, which runs continuously, e.g. to develop against it). There is an example how to achieve it.

## Logging

Restito uses slf4j as a logging abstraction, which by default does not have any implementations: it collect logs It allows you to receive all the logging via the library, that your application is using.