A configurable server for stubbing external systems during development. Uses Node.js and written in Coffeescript
npm install -g stubby
This will install stubby as a command in your PATH. Leave off the -g flag if you'd like to use stubby as an embedded module in your project.
You need to have coffee-script installed on your system.
git clone git://github.com/Afmrak/stubby4node.git
cd stubby4node
coffee -o lib -c src
export PATH=$PATH:<pwd>/bin/stubby
Development is on Mac OS X Mountain Lion.
Some systems require you to sudo before running services on port certain ports (like 80)
[sudo] stubby
stubby [-a <port>] [-c <file>] [-d <file>] [-h] [-k <file>] [-l <hostname>] [-m] [-p <file>]
[-s <port>] [-t <port>] [-v] [-w]
-a, --admin <port> Port for admin portal. Defaults to 8889.
-c, --cert <file> Certificate file. Use with --key.
-d, --data <file> Data file to pre-load endoints. YAML or JSON format.
-h, --help This help text.
-k, --key <file> Private key file. Use with --cert.
-l, --location <hostname> Hostname at which to bind stubby.
-m, --mute Prevent stubby from printing to the console.
-p, --pfx <file> PFX file. Ignored if used with --key/--cert
-s, --stubs <port> Port for stubs portal. Defaults to 8882.
-t, --tls <port> Port for https stubs portal. Defaults to 7443.
-v, --version Prints stubby's version number.
-w, --watch Auto-reload data file when edits are made.
This section explains the usage, intent and behavior of each property on the request and response objects.
Here is a fully-populated, unrealistic endpoint:
- request:
url: ^/your/awesome/endpoint$
method: POST
query:
exclamation: post requests can have query strings!
headers:
content-type: application/xml
post: >
<!xml blah="blah blah blah">
<envelope>
<unaryTag/>
</envelope>
file: tryMyFirst.xml
response:
status: 200
latency: 5000
headers:
content-type: application/xml
server: stubbedServer/4.2
body: >
<!xml blah="blah blah blah">
<responseXML>
<content></content>
</responseXML>
file: responseData.xml
This object is used to match an incoming request to stubby against the available endpoints that have been configured.
localhost:8882).query is for).
/url?some=value&another=value becomes /url
This is the simplest you can get:
- request:
url: /
A demonstration using regular expressions:
- request:
url: ^/has/to/begin/with/this/
- request:
url: /has/to/end/with/this/$
- request:
url: ^/must/be/this/exactly/with/optional/trailing/slash/?$
GET.- request:
url: /anything
method: GET
- request:
url: /anything
method: [GET, HEAD]
- request:
url: ^/yonder
method:
- GET
- HEAD
- POST
allows the query parameters to appear in any order in a uri
The following will match either of these:
/with/parameters?search=search+terms&filter=month/with/parameters?filter=month&search=search+terms- request:
url: ^/with/parameters$
query:
search: search terms
filter: month
- request:
url: ^/post/form/data$
post: name=John&email=john@example.com
post with the contents of the locally given file.
post for matching.- request:
url: ^/match/against/file$
file: postedData.json
post: '{"fallback":"data"}'
postedData.json
{"fileContents":"match against this if the file is here"}
postedData.json doesn't exist on the filesystem when /match/against/file is requested, stubby will match post contents against {"fallback":"data"} (from post) instead.query.The following endpoint only accepts requests with application/json post values:
- request:
url: /post/json
method: post
headers:
content-type: application/json
Assuming a match has been made against the given request object, data from response is used to build the stubbed response back to the client.
200.- request:
url: ^/im/a/teapot$
method: POST
response:
status: 420
- request:
url: ^/give/me/a/smile$
response:
body: ':)'
request.file, but the contents of the file are used as the body.- request:
url: /
response:
file: extremelyLongJsonFile.json
request.headers except that these are sent back to the client.- request:
url: ^/give/me/some/json$
response:
headers:
content-type: application/json
body: >
[{
"name":"John",
"email":"john@example.com"
},{
"name":"Jane",
"email":"jane@example.com"
}]
- request:
url: ^/hello/to/jupiter$
response:
latency: 800000
body: Hello, World!
The admin portal is a RESTful(ish) endpoint running on localhost:8889. Or wherever you described through stubby's options.
Submit POST requests to localhost:8889 or load a data-file (-d) with the following structure for each endpoint:
request: describes the client's call to the server
method: GET/POST/PUT/DELETE/etc.url: the URI regex string. GET parameters should also be included inline herequery: a key/value map of query string parameters included with the requestheaders: a key/value map of headers the server should respond topost: a string matching the textual body of the response.file: if specified, returns the contents of the given file as the request post. If the file cannot be found at request time, post is used insteadresponse: describes the server's response to the client
headers: a key/value map of headers the server should use in it's responselatency: the time in milliseconds the server should wait before responding. Useful for testing timeouts and latencyfile: if specified, returns the contents of the given file as the response body. If the file cannot be found at request time, body is used insteadbody: the textual body of the server's response to the clientstatus: the numerical HTTP status code (200 for OK, 404 for NOT FOUND, etc.)- request:
url: ^/path/to/something$
method: POST
headers:
authorization: "Basic usernamez:passwordinBase64"
post: this is some post data in textual format
response:
headers:
Content-Type: application/json
latency: 1000
status: 200
body: You're request was successfully processed!
- request:
url: ^/path/to/anotherThing
query:
a: anything
b: more
method: GET
headers:
Content-Type: application/json
post:
response:
headers:
Content-Type: application/json
Access-Control-Allow-Origin: "*"
status: 204
file: path/to/page.html
- request:
url: ^/path/to/thing$
method: POST
headers:
Content-Type: application/json
post: this is some post data in textual format
response:
headers:
Content-Type: application/json
status: 304
[
{
"request": {
"url": "^/path/to/something$",
"post": "this is some post data in textual format",
"headers": {
"authorization": "Basic usernamez:passwordinBase64"
},
"method": "POST"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"latency": 1000,
"body": "You're request was successfully processed!"
}
},
{
"request": {
"url": "^/path/to/anotherThing",
"query": {
"a": "anything",
"b": "more"
},
"headers": {
"Content-Type": "application/json"
},
"post": null,
"method": "GET"
},
"response": {
"status": 204,
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
"file": "path/to/page.html"
}
},
{
"request": {
"url": "^/path/to/thing$",
"headers": {
"Content-Type": "application/json"
},
"post": "this is some post data in textual format",
"method": "POST"
},
"response": {
"status": 304,
"headers": {
"Content-Type": "application/json"
}
}
}
]
If you want to load more than one endpoint via file, use either a JSON array or YAML list (-) syntax. On success, the response will contain Location in the header with the newly created resources' location
Performing a GET request on localhost:8889 will return a JSON array of all currently saved responses. It will reply with 204 : No Content if there are none saved.
Performing a GET request on localhost:8889/<id> will return the JSON object representing the response with the supplied id.
You can also view the currently configured endpoints by going to localhost:8889/status
Perform PUT requests in the same format as using POST, only this time supply the id in the path. For instance, to update the response with id 4 you would PUT to localhost:8889/4.
Send a DELETE request to localhost:8889/<id>
Requests sent to any url at localhost:8882 (or wherever you told stubby to run) will search through the available endpoints and, if a match is found, respond with that endpoint's response data
For a given endpoint, stubby only cares about matching the properties of the request that have been defined in the YAML. The exception to this rule is method; if it is omitted it is defaulted to GET.
For instance, the following will match any POST request to the root url:
- request:
url: /
method: POST
response: {}
The request could have any headers and any post body it wants. It will match the above.
Pseudocode:
for each <endpoint> of stored endpoints {
for each <property> of <endpoint> {
if <endpoint>.<property> != <incoming request>.<property>
next endpoint
}
return <endpoint>
}
Add stubby as a module within your project's directory:
npm install stubby
Then within your project files you can do something like:
var Stubby = require('stubby').Stubby;
var mockExternalService = new Stubby();
mockService.start();
What can I do with it, you ask? Read on!
options: an object containing parameters with which to start this stubby. Parameters go along with the full-name flags used from the command line.
stubs: port number to run the stubs portaladmin: port number to run the admin portaltls: port number to run the stubs portal over httpsdata: JavaScript Object/Array containing endpoint datalocation: address/hostname at which to run stubbykey: keyfile contents (in PEM format)cert: certificate file contents (in PEM format)pfx: pfx file contents (mutually exclusive with key/cert options)watch: filename to monitor and load as stubby's data when changes occurmute: defaults to true. Pass in false to have console output (if available)callback: takes one parameter: the error message (if there is one), undefined otherwiseIdentical to previous signature, only all options are assumed to be defaults.
closes the connections and ports being used by stubby's stubs and admin portals. Executes callback afterward.
Simulates a GET request to the admin portal, with the callback receiving the resultant data.
id: the id of the endpoint to retrieve. If ommitted, an array of all registered endpoints is passed the callback.callback(err, endpoint): err is defined if no endpoint exists with the given id. Else, endpoint is populated.Simulates a GET request to the admin portal, with the callback receiving the resultant data.
id: the id of the endpoint to retrieve. If ommitted, an array of all registered endpoints is passed the callback.callback(endpoints): takes a single parameter containing an array of returned results. Empty if no endpoints are registereddata: an endpoint object to store in stubbycallback(err, endpoint): if all goes well, gets executed with the created endpoint. If there is an error, gets called with the error message.id: id of the endpoint to update.data: data with which to replace the endpoint.callback(err): executed with no passed parameters if successful. Else, passed the error message.id: id of the endpoint to destroy. If ommitted, all endoints are cleared from stubby.callback(): called after the endpoint has been removedStubby = require('stubby').Stubby
stubby1 = new Stubby()
stubby2 = new Stubby()
stubby1.start
stubs: 80
admin: 81
location: 'localhost'
data: [
request:
url: "/anywhere"
,
request:
url: "/but/here"
]
stubby2.start
stubs: 82
admin: 83
location: '127.0.0.2'
If you don't have jasmine-node already, install it:
npm install -g jasmine-node
From the root directory run:
jasmine-node --coffee spec
If you want to see more informative output:
jasmine-node --verbose --coffee spec
post parameter as a hashmap under request for easy form-submission value matchingnpm module?