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
- Starting and stopping stub server
- Stubbing server behavior
- Verifying calls to server
- Using like a standalone stub server
- Logging
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();
}
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());
}
When you need to use HTTPS, this is just one configuration call...
See UsingHttpsTest.
server = new StubServer().secured().run();
!! 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 actionsAction 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 actionsYou 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.
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
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 actionsSometimes you need to have different responses based on the sequence number of a request.
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.
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
.
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).
Credits to @shamoh for this feature.
## Autodiscovery of stubs contentThis 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 verificationsTo 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.
## Limiting number of callsYou have more options to limit number of calls:
See LimitingNumberOfCallsTest.
## Sequenced verificationsYou 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 serverIt 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.
## LoggingRestito 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.