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
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.
@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());
}
Restito comes with a built-in self-signed certificate (to override it use javax.net.ssl.keyStore
and javax.net.ssl.keyStorePassword
), so using HTTPS is just one configuration call:
var server = new StubServer().secured().run();
You can also configure mutual authentication like this:
server.clientAuth(keystoreBytes, keystorePass).run();
Check UsingHttpsTest for full examples of confguration, assertions and advanced usage like configuring client to trust server's certification or client authentication (a.k.a., Mutual TLS or mTLS).
!! 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.
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.
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.
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.
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.
Sometimes 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.
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.
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.
You have more options to limit number of calls:
See LimitingNumberOfCallsTest.
You can easily chain verifications:
verifyHttp(server).once(
method(Method.GET),
uri("/first")
).then().once(
method(Method.GET),
uri("/second")
);
See SequencedVerificationsTest.
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.
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.