Comparing version 1.0.2 to 1.2.0
var express = require('express'); | ||
var path = require('path'); | ||
var FluentInterface = require('./fluent'); | ||
var corsMiddleware = require('./cors'); | ||
@@ -10,4 +12,6 @@ function createInvalidDataException(data) { | ||
o = o || { debug: false }; | ||
var debug = require('./debug')('interfake-server', o.debug); | ||
var app = express(); | ||
var server; | ||
var fluentInterface = new FluentInterface(this, o); | ||
@@ -17,9 +21,10 @@ app.configure(function(){ | ||
app.use(express.urlencoded()); | ||
app.use(corsMiddleware); | ||
app.use(app.router); | ||
}); | ||
app.post('/_request', function(req, res){ | ||
app.post('/_requests?', function(req, res){ | ||
try { | ||
createRoute(req.body); | ||
res.send(200, { done : true }); | ||
res.send(201, { done : true }); | ||
} catch (e) { | ||
@@ -31,23 +36,35 @@ debug('Error: ', e); | ||
function debug () { | ||
if (o.debug) { | ||
console.log.apply(console, arguments); | ||
} | ||
} | ||
function indexOfRequestRoute(request) { | ||
var i; | ||
if (!app.routes[request.method]) return -1; | ||
this.debug = debug; | ||
function clearRouteForRequest(request) { | ||
var i,j; | ||
if (!app.routes[request.method]) return; | ||
for (i = app.routes[request.method].length - 1; i >= 0; i--) { | ||
if (app.routes[request.method][i].path === request.url) { | ||
debug('Clearing existing route at ' + request.method + ': ' + request.url + ' (1/2)'); | ||
app.routes[request.method].splice(i); | ||
break; | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
function createRoute (data) { | ||
function clearRouteForRequest(request) { | ||
var i = indexOfRequestRoute(request); | ||
if (i === -1) { | ||
return; | ||
} | ||
app.routes[request.method].splice(i); | ||
} | ||
function setRouteProperty(request, property, value) { | ||
var i = indexOfRequestRoute(request); | ||
if (i === -1) { | ||
return; | ||
} | ||
app.routes[request.method][i][property] = value; | ||
} | ||
function createRoute(data) { | ||
var specifiedRequest, specifiedResponse, afterSpecifiedResponse; | ||
@@ -58,2 +75,4 @@ if (!data.request || !data.request.method || !data.request.url || !data.response || !data.response.code) { | ||
// debug('Setup time', JSON.stringify(data, null, 4)); | ||
if (data.response.body) { | ||
@@ -64,7 +83,5 @@ debug('Setting up ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with a body of length ' + JSON.stringify(data.response.body).length); | ||
} | ||
// debug('ROUTER obj: \n' + JSON.stringify(app._router.map)); | ||
debug('After response data set?', !!data.afterResponse); | ||
specifiedRequest = data.request; | ||
specifiedResponse = data.response; | ||
afterSpecifiedResponse = data.afterResponse; | ||
@@ -74,4 +91,9 @@ clearRouteForRequest(specifiedRequest); | ||
app[specifiedRequest.method](specifiedRequest.url, function (req, res) { | ||
var specifiedResponse = req.route.responseData; | ||
var afterSpecifiedResponse = req.route.afterResponseData; | ||
debug(req.method, 'request to', req.url, 'returning', specifiedResponse.code); | ||
// debug('After response is', afterSpecifiedResponse); | ||
var responseBody = specifiedResponse.body; | ||
debug('Request to ' + specifiedRequest.url); | ||
@@ -89,2 +111,3 @@ res.setHeader('Content-Type', 'application/json'); | ||
if (afterSpecifiedResponse && afterSpecifiedResponse.endpoints) { | ||
debug('Response sent, setting up', afterSpecifiedResponse.endpoints.length, 'endpoints'); | ||
afterSpecifiedResponse.endpoints.forEach(function (endpoint) { | ||
@@ -95,2 +118,12 @@ createRoute(endpoint); | ||
}); | ||
setRouteProperty(specifiedRequest, 'responseData', data.response); | ||
setRouteProperty(specifiedRequest, 'afterResponseData', data.afterResponse); | ||
if (data.response.body) { | ||
debug('Setup complete: ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with a body of length ' + JSON.stringify(data.response.body).length); | ||
} else { | ||
debug('Setup complete: ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with no body'); | ||
} | ||
debug('After response data set?', !!data.afterResponse); | ||
} | ||
@@ -100,2 +133,12 @@ | ||
this.get = fluentInterface.forMethod('get'); | ||
this.post = fluentInterface.forMethod('post'); | ||
this.put = fluentInterface.forMethod('put'); | ||
this.delete = fluentInterface.forMethod('delete'); | ||
this.serveStatic = function (path, directory) { | ||
path = path || '/_static'; | ||
app.use(path, express.static(directory)); | ||
}; | ||
this.listen = function (port) { | ||
@@ -102,0 +145,0 @@ port = port || 3000; |
{ | ||
"name": "interfake", | ||
"preferGlobal": true, | ||
"version": "1.0.2", | ||
"version": "1.2.0", | ||
"author": "Daniel Hough <daniel.hough@gmail.com>", | ||
@@ -37,3 +37,3 @@ "description": "A simple way to create dummy APIs", | ||
"dependencies": { | ||
"express": "^3.4.5", | ||
"express": "^3.4.8", | ||
"commander": "2.1.0" | ||
@@ -49,6 +49,8 @@ }, | ||
"api-easy": "^0.3.8", | ||
"vows": "^0.7.0", | ||
"mocha": "^1.18.0", | ||
"request": "^2.34.0", | ||
"q": "^1.0.1" | ||
"q": "^1.0.1", | ||
"zombie": "^2.0.0-alpha31", | ||
"connect": "^2.14.2" | ||
} | ||
} |
262
readme.md
@@ -1,68 +0,139 @@ | ||
# Interfake: Mocked JSON APIs for any platform | ||
# Interfake: Quick JSON APIs | ||
Interfake is a tool which allows developers of client-side applications to easily create dummy APIs to develop against. Let's get started with a simple example. | ||
Interfake is a tool which allows developers of client-side applications of *any* platform to easily create dummy HTTP APIs to develop against. Let's get started with a simple example. | ||
## Get Started | ||
## Get started | ||
Install Interfake globally: | ||
If you don't want to use the JavaScript method to create your Interfake API, go read up on [all the methods](#three-ways-to-use-interfake). Otherwise, read on. | ||
Install Interfake in your project. | ||
``` | ||
npm install interfake -g | ||
npm install interfake --save | ||
``` | ||
Create a file called `adventuretime.json`: | ||
Let's write a simple fake API: | ||
```JSON | ||
[ | ||
{ | ||
"request": { | ||
"url": "/whattimeisit", | ||
"method": "get" | ||
}, | ||
"response": { | ||
"code": 200, | ||
"body": { | ||
"theTime": "Adventure Time!", | ||
"starring": [ | ||
"Finn", | ||
"Jake" | ||
], | ||
"location": "ooo" | ||
} | ||
} | ||
} | ||
] | ||
```js | ||
var Interfake = require('interfake'); | ||
var interfake = new Interfake(); | ||
interfake.get('/whats-next').body({ next : 'more stuff '}); | ||
interfake.listen(3000); // The server will listen on port 3000 | ||
``` | ||
Then run Interfake against it: | ||
Now go to http://localhost:3000/whats-next in your browser (or [`curl`](http://curl.haxx.se)), and you will see the following: | ||
```json | ||
{ | ||
"next":"more stuff" | ||
} | ||
``` | ||
interfake --file ./adventuretime.json | ||
You can also chain response properties: | ||
```js | ||
var Interfake = require('interfake'); | ||
var interfake = new Interfake(); | ||
interfake.get('/whats-next').status(400).body({ error : 'such a bad request'}); | ||
interfake.listen(3000); | ||
/* | ||
# Request: | ||
curl http://localhost:3000/whats-next -X GET | ||
# Response: | ||
400 | ||
{ | ||
"error":"such a bad request" | ||
} | ||
*/ | ||
``` | ||
Then using [`curl`](http://curl.haxx.se): | ||
You can use different HTTP methods: | ||
```js | ||
var Interfake = require('interfake'); | ||
var interfake = new Interfake(); | ||
interfake.post('/next-items').status(201).body({ created : true }); | ||
interfake.listen(3000); | ||
/* | ||
# Request: | ||
curl http://localhost:3000/next-items -X POST | ||
# Response: | ||
201 | ||
{ | ||
"created":true | ||
} | ||
*/ | ||
``` | ||
curl http://localhost:3000/whattimeisit --verbose | ||
And you can specify endpoints which should only be created once other ones have been hit (conditional endpoints): | ||
```js | ||
var Interfake = require('interfake'); | ||
var interfake = new Interfake(); | ||
var postResponse = interfake.post('/next-items').status(201).body({ created : true }); | ||
postResponse.creates.get('/items/1').status(200).body({ id: 1, name: 'Item 1' }); | ||
postResponse.creates.get('/next-items').status(200).body({ items: [ { id: 1, name: 'Item 1' } ] }); | ||
interfake.listen(3000); | ||
/* | ||
# Request: | ||
curl http://localhost:3000/next-items -X POST | ||
# Response: | ||
201 | ||
{ | ||
"created":true | ||
} | ||
# Request: | ||
curl http://localhost:3000/items/1 -X GET | ||
# Response: | ||
200 | ||
{ | ||
"id":1 | ||
"name":"Item 1" | ||
} | ||
*/ | ||
``` | ||
Or go to http://localhost:3000/whattimeisit in your web browser. | ||
For JavaScript developers | ||
The above example will create a endpoint at `http://localhost:3000/whattimeisit` which returns a `200` and the body specified in the `response` object. | ||
--- | ||
Run `interfake -?` for a full list of command-line options. | ||
## Three ways to use Interfake | ||
----- | ||
Interfake can handle complex API structures, mutable endpoints and has three interfaces: the [JavaScript API](#method-1-javascript) (useful for NodeJS-based tests), the [command line](#method-2-command-line) (useful for non-NodeJS tests), or on-the-fly using Interfake's [HTTP meta-API](#method-2-command-line) (also useful for non-NodeJS tests). Based on [express](https://github.com/visionmedia/express). | ||
Interfake allows for complex API structures, dynamic response endpoints and has three interfaces: the [JavaScript API](#method-1-javascript) (useful for tests), the [command line](#method-2-command-line) (like above), or on-the-fly using Interfake's [HTTP meta-API](#method-2-command-line). | ||
## Method 1: JavaScript | ||
Make sure you've install Interfake as a local module using `npm install interfake --save`. Then, you can start doing things like this: | ||
Make sure you've install Interfake as a local module using `npm install interfake --save`. If you `var Interfake = require('interfake')` in your JavaScript file, you can use the following API to spin up the Interfake server. | ||
### API | ||
* `new Interfake(options)`: creates an Interfake object. Options are: | ||
* `debug`: If `true`, outputs lots of annoying but helpful log messages. Default is `false`. | ||
* `#createRoute(route)`: Takes a JSON object with `request`, `response` and optionally `afterResponse` properties | ||
* `#listen(port)`: Takes a port and starts the server | ||
* `#stop()`: Stops the server if it's been started | ||
#### Fluent Interface | ||
* `#get|post|put|delete(url)`: Create an endpoint at the specified URL. Can then be followed by each of the following, which can follow each other too e.g. `get().body().status().body()` | ||
* `#status(statusCode)`: Set the response status code for the endpoint | ||
* `#body(body)`: Set the JSON response body of the end point | ||
* `#create#get|post|put|delete(url)`: Specify an endpoint to create *after* the first execution of this one. API is the same as above. | ||
### Example ([more examples](/examples-javascript)) | ||
```javascript | ||
var Interfake = require('interfake'); | ||
var request = require('request'); // Let's use request for this example | ||
var interfake = new Interfake(); | ||
// Create endpoints using the fluent interface | ||
interfake.post('/items').status(201).body({ created: true }).creates.get('/next-items').status(200).body([ { id: 1, name: 'the thing' } ]); | ||
// Or using the more verbose JSON syntax (more on this below under 'command line') | ||
interfake.createRoute({ | ||
@@ -81,8 +152,3 @@ request: { | ||
interfake.listen(3030); // The server will listen on port 3030 | ||
request('http://localhost:3030/whats-next', function (error, response, body) { | ||
console.log(response.statusCode); // prints 200 | ||
console.log(body); // prints { next: "more stuff" } | ||
}); | ||
interfake.listen(3000); // The server will listen on port 3000 | ||
``` | ||
@@ -97,7 +163,25 @@ | ||
* `#stop()`: Stops the server if it's been started | ||
* `#serveStatic(path, directory)`: Serve static (usually a website) files from a certain path. This is useful for testing [SPAs](http://en.wikipedia.org/wiki/Single-page_application). ([Example use.](/examples-javascript/fluent-web-page-test.js)) | ||
#### Fluent Interface | ||
* `#get|post|put|delete(url)`: Create an endpoint at the specified URL. Can then be followed by each of the following, which can follow each other too e.g. `get().body().status().body()` | ||
* `#status(statusCode)`: Set the response status code for the endpoint | ||
* `#body(body)`: Set the JSON response body of the end point | ||
* `#create#get|post|put|delete(url)`: Specify an endpoint to create *after* the first execution of this one. API is the same as above. | ||
## Method 2: Command line | ||
Create a file from this template: | ||
Interfake must be install globally for the command line interface to work: | ||
``` | ||
npm install interfake -g | ||
``` | ||
A JSON array of request/response pairs ("endpoints") you can write APIs and run them multiple times using the global `interfake` executable, and the JSON syntax. | ||
### Example ([more examples](/examples-command-line)) | ||
Create a file called `adventuretime.json`: | ||
```JSON | ||
@@ -107,8 +191,15 @@ [ | ||
"request": { | ||
"url": "", | ||
"method": "" | ||
"url": "/whattimeisit", | ||
"method": "get" | ||
}, | ||
"response": { | ||
"code": 200, | ||
"body": {} | ||
"body": { | ||
"theTime": "Adventure Time!", | ||
"starring": [ | ||
"Finn", | ||
"Jake" | ||
], | ||
"location": "ooo" | ||
} | ||
} | ||
@@ -119,2 +210,12 @@ } | ||
Then run Interfake against it: | ||
``` | ||
interfake --file ./adventuretime.json | ||
``` | ||
Now go to http://localhost:3000/whattimeisit in your web browser. | ||
The above example will create a endpoint at `http://localhost:3000/whattimeisit` which returns a `200` and the body specified in the `response` object. | ||
The top-level array should contain a list of endpoints (represented by request/response objects). The `request` object contains a URL and HTTP Method (GET/POST/PUT/DELETE/etc) to match against, and the `response` object contains an [HTTP Status Code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) (`code`) and `body` object, which is in itself a JSON object, and optional. This `body` is what will be returned in the body of the response for the request you're creating. | ||
@@ -124,12 +225,8 @@ | ||
Then, run the server like so: | ||
Run `interfake -?` for a full list of command-line options. | ||
``` | ||
interfake ./path/to/file.json | ||
``` | ||
### Conditional endpoints | ||
### Dynamic Response Endpoints | ||
When the API needs to mutate responses, such as after a POST, PUT or DELETE request, there is an `afterResponse` property available for any existing endpoint, which specifies endpoints to create after the parent has been hit for the first time. | ||
For situations where the API needs to react to mutated data, such as after a POST, PUT or DELETE request, there is an `afterResponse` property available for any existing endpoint. In this object, create another array of endpoints to be created after the original one has been created, like so: | ||
```JSON | ||
@@ -139,7 +236,7 @@ [ | ||
"request": { | ||
"url": "", | ||
"method": "" | ||
"url": "/items", | ||
"method": "post" | ||
}, | ||
"response": { | ||
"code": 200, | ||
"code": 201, | ||
"body": {} | ||
@@ -151,8 +248,8 @@ }, | ||
"request": { | ||
"url": "", | ||
"method": "" | ||
"url": "/items", | ||
"method": "get" | ||
}, | ||
"response": { | ||
"code": 200, | ||
"body": {} | ||
"body": { items : [] } | ||
} | ||
@@ -166,7 +263,7 @@ } | ||
The `afterResponse` property can be used as deep as you like in the endpoint hierarchy. For a complex example of the use of post-response endpoints, see the `/example-apis/crud.json` file in this repository. | ||
The `afterResponse` property can be used as deep as you like in the endpoint hierarchy. For a complex example of the use of post-response endpoints, see the `/examples-command-line/crud.json` file in this repository. | ||
## Method 3: HTTP | ||
While the server is running, you can create new endpoints on-the-fly. You can make a POST request to `/_request` with a string containing the same JSON structure as above. If you were using `curl`, this is an example (smaller, for brevity). | ||
While the server is running, you can create new endpoints on-the-fly. You can make a POST request to `/_requests` with the same JSON structure that the command line interface accepts. | ||
@@ -178,3 +275,3 @@ ### Example | ||
``` | ||
curl -X POST -d '{ "request":{"url":"/whattimeisit", "method":"get"}, "response":{"code":200,"body":{ "theTime" : "Adventure Time!" } } }' http://localhost:3000/_request --header "Content-Type:application/json" | ||
curl -X POST -d '{ "request":{"url":"/whattimeisit", "method":"get"}, "response":{"code":200,"body":{ "theTime" : "Adventure Time!" } } }' http://localhost:3000/_requests --header "Content-Type:application/json" | ||
``` | ||
@@ -184,10 +281,8 @@ | ||
If you'd like the response to come back as [JSONP](http://en.wikipedia.org/wiki/JSONP) (so, for example you are trying to make a cross-origin request without using CORS) then specify a `callback` query parameter, like so: | ||
Interfake supports [JSONP](http://en.wikipedia.org/wiki/JSONP). Just put `?callback` on the end of the URLs being called. | ||
``` | ||
curl http://localhost:3000/whattimeisit?callback=handleSomeJson --verbose | ||
curl http://localhost:3000/whattimeisit?callback=handleSomeJson | ||
``` | ||
If you inject this code into your webpage the `handleSomeJson` method will be called with the data. | ||
## Use Cases | ||
@@ -205,2 +300,4 @@ | ||
For an example of how to do this, please see the [web page test example](/examples-javascript/fluent-web-page-test.js). | ||
### Creating a static API | ||
@@ -216,3 +313,5 @@ | ||
* 1.0.2: Bugfix: Postresponse was removing the wrong old endpoint | ||
* 1.2.0: Added ability to do static files | ||
* 1.1.1: Fixed the response to `POST /_request` to be a 201, and `POST /_requests` is now the path used | ||
* 1.1.0: Added the fluent interface for easier creation of endpoints | ||
* 1.0.0: Backwards-incompatible changes for JavaScript API, now creating an `Interfake` instance | ||
@@ -224,11 +323,28 @@ * 0.2.0: Added JSONP support | ||
Interfake is a labour of love, created for front-end and mobile developers to increase their prototyping and development speeds. If you can contribute by getting through some issues, I would be very grateful. <3 Open Source! | ||
Interfake is a labour of love, created for front-end and mobile developers to increase their prototyping and development speeds. If you can contribute by getting through some issues, I would be very grateful. | ||
If you make any pull requests, please do try to write tests, or at the very least ensure they're still passing by running `npm test` before you do so. | ||
<3 Open Source! | ||
[![I Love Open Source](http://www.iloveopensource.io/images/logo-lightbg.png)](http://www.iloveopensource.io/projects/5319884587659fce66000943) | ||
## Plans for this module | ||
## Dependencies | ||
* Better test coverage | ||
* [express](https://github.com/visionmedia/express) | ||
* [commander](https://github.com/visionmedia/commander.js/) | ||
## Thank yous | ||
[Alun](https://github.com/4lun) for reading this readme. | ||
## Author | ||
[Dan Hough](https://github.com/basicallydan) ([Twitter](https://twitter.com/basicallydan) | [Website](http://danielhough.co.uk)) | ||
## Future work | ||
* Create a guide/some examples for how to integrate this with existing test frameworks, whether written in JavaScript or not | ||
* Improve the templating, so that a response might include a repeated structure with an incrementing counter or randomized data | ||
* Create a way to add static files in case you'd like to run a JavaScript application against it | ||
* Allow custom headers to be set | ||
* Mimic slow responses |
20550
7
227
338
6
Updatedexpress@^3.4.8