Holoserve - Simple faking of HTTP APIs¶ ↑
This tool can be used to fake HTTP web APIs. It’s meant to be used in a testing environment, to make the test suite run faster and be independent from other API and network problems.
Concept¶ ↑
HoloServe runs a Goliath application server, that matches any incoming request to a list of request/response pairs. If a match is found, the corresponding response is returned. To do that the response is assembeled by a selection of responses. The selection is made by conditions on the current state. The name of the matched request/response pair is saved in a request history. If no match is found, a 404 is returned and the request data is stored in the bucket for unhandeled requests. These informations can be used to extend the server layout with missing request handlers.
To avoid too much duplication in the definition of the request/response-pairs, it is possible reference some fixture data that is shared between all pair definitions.
The pairs, state, history and bucket can be accessed via control routes, which are described below.
Installation¶ ↑
Assuming that ruby
and gem
are installed, simply type…
gem install holoserve
Run from the command line¶ ↑
To start up an empty Holoserve instance, type…
holoserve
To load the server with a couple of pairs, fixtures and define a state during start up, use these parameters.
holoserve -f 'path/to/fixtures/*.yaml' -p 'path/to/pairs/*.yaml' -s user_one=missing -s car_one=existing
Notice, that the files must have either the .yaml
or the .json
extension.
A full description of all options can be displayed with holoserve --help
-P, --port PORT The port holoserve should listen to. -f, --fixture-files PATTERN Load the specified fixture files on startup. -p, --pair-files PATTERN Load the specified pair files on startup. -s, --state SETTING Set a specific state. Use the pattern key=value. Can be applied multiple times. -h, --help Shows the help message.
Control routes¶ ↑
If you’re using Ruby, you can control Holoserve via the Holoserve Connector gem.
GET /_control/pairs¶ ↑
Returns all pair definitions.
GET /_control/pairs/:id¶ ↑
Returns the specified pair definition.
PUT /_control/state¶ ↑
Sets the current state with the transmitted parameters.
GET /_control/state¶ ↑
Returns the current state.
Response example¶ ↑
{ "user_one": "missing", "car_one": "existing" }
GET /_control/bucket¶ ↑
Returns a list of all requests that has been received, but couldn’t be handled.
Response example¶ ↑
[ { "method": "GET", "path": "/test", "headers": { "SERVER_SOFTWARE": "Goliath", "SERVER_NAME": "localhost", "SERVER_PORT": "4250", "REMOTE_ADDR": "127.0.0.1", "HTTP_ACCEPT": "*/*", "HTTP_USER_AGENT": "Ruby", "HTTP_HOST": "localhost:4250", "HTTP_VERSION": "1.1", "SCRIPT_NAME": "/test" } } ]
GET /_control/history¶ ↑
Returns a list of hashes, which include the names/id of pairs that has been triggered, with a request variant and a list of response variants.
Response example¶ ↑
[{"id":"test_request","request_variant":"default","response_variants":["default","alternative"]}]
DELETE /_control/history¶ ↑
Removes all entries from the history.
Pair file format¶ ↑
The request/response pair file should have the following format.
requests: default: imports: - path: "test_fixtures.users.0" as: "parameters" only: [ "username", "password" ] method: "POST" path: "/session" headers: HTTP_USER_AGENT: "Ruby" oauth: oauth_token: "12345" responses: default: status: 200 found: condition: "username == :existing" imports: - path: "test_fixtures.users.0" as: "json.user" transitions: session_one: "existing" not_found: condition: "username == :missing" json: message: "user not found"
The fixture data where this example pair definition relies on, could look like the following.
users: - email: "one@test.com" username: "one" password: "valid"
This example defines a request/response pair situation. It uses username=one
and password=valid
as parameters.
If the state includes condition: "user_one == :existing"
, the response would return the complete user object encoded as json. If the state includes user_one == :missing
, the request would be replied with the message “user not found”.
The transitions
part of the response allows you set the successor state. If a response is selected, the corresponding transitions will be evaluated.
requests: default: imports: - path: "test_fixtures.users.0.email" as: "parameters" method: "POST" path: "/valid_email" responses: default: status: 200 found: condition: "email == :existing" imports: - path: "test_fixtures.users.0.username" as: "json.user.username" transitions: session_one: "existing" not_found: condition: "email == :missing" json: message: "email address not found"
This example relies on the same fixture file used above.
This time it uses email=one@test.com
as a parameter. If the state includes condition: "email == :existing"
, the response would return a username of the email address owner as json.
If the state includes email == :missing
, the response would be a message “email address not found” in json.