Skip to content

Commit

Permalink
Merge pull request #20 from ldabiralai/callback-or-promise
Browse files Browse the repository at this point in the history
Implement promised based API
  • Loading branch information
ldabiralai authored Feb 2, 2017
2 parents e8a8e99 + a6b3b24 commit b4b73fd
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 56 deletions.
41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ simulado
```timeout``` defaults to ```0``` so there will be no delay, accepts seconds. If it's specified, simulado will wait and then send a response.

### Mock
The ```callback``` will be called once Simulado has finished mocking the endpoint. You should probably put the rest of your step in a function here.
The `mock` will return a promise which will be fulfilled once the Simulado has finished mocking the endpoint.
You may chain requests using `then` or `await` the call if you're inside an `async` function (See https://babeljs.io/docs/plugins/transform-async-to-generator/).
```javascript
Simulado.mock({
path: '/account/devices',
Expand All @@ -41,8 +42,9 @@ Simulado.mock({
type: "MOBILE",
name: "My work phone"
}
}, callback)
})
```

##### Wildcards
```javascript
Simulado.mock({
Expand All @@ -54,7 +56,7 @@ Simulado.mock({
type: "MOBILE",
name: "My work phone"
}
}, callback)
})
```
<code>GET localhost.com/account/path-here => OK 200</code>

Expand Down Expand Up @@ -85,21 +87,20 @@ Simulado.mocks([
]
}
}
], callback)
])
```


### Getting the last request
You can retrive the request made to an endpoint with ```Simulado.lastRequest(httpMethod, path)```
For instance, using `async/await`
```javascript
Simulado.lastRequest('POST', '/postingPath', function(err, lastRequestMade) {
lastRequestMade.headers // => {"Content-Type": "application/json"}
lastRequestMade.body // => {"name": "simulado"}

// http://localhost:7000/postingPath?paramName=value
lastRequestMade.params // => {"paramName": "value"}
})
const lastRequestMade = await Simulado.lastRequest('POST', '/postingPath');
console.log(lastRequestMade.headers); // => {"Content-Type": "application/json"}
console.log(lastRequestMade.body); // => {"name": "simulado"}

// when called with: http://localhost:7000/postingPath?paramName=value
console.log(lastRequestMade.params); // => {"paramName": "value"}
```
or you can make a request to ```http://localhost:7000/lastRequest``` with two headers (method and path), which will respond with the last request as JSON.
Example (using superagent)
Expand All @@ -112,10 +113,28 @@ superagent.get('http://localhost:7000/lastRequest')
res.body.headers // => {"paramName": "value"}
});
```

### Callbacks
The old style `callbacks` are still available on all calls if you prefer to use them, but are now deprecated.
The `callback` is always the last parameter and will be called once the method has completed or failed.
For instance:
```javascript
Simulado.lastRequest(function(error, result) {
if (error) {
console.error(error);
} else {
var lastRequestMade = result.body;
console.log(lastRequestMade.headers); // => {"paramName": "value"}
}
});
```

### Use
After mocking, you can call the endpoint whichever way you like. Simulado starts a server on ```localhost:7001``` the path you specify is relative to this.

### Viewing mocked reponses
To inspect all the mocked endpoints you can goto `http://localhost:7001/inspect`. You can use these enpoints while developing your app by making an API call the `http://localhost:7001/[path]`.

### Custom URL
If you want to host simulado on a remote machine, you can require the remote API implementation which allows you to customize the endpoint.
Example:
Expand Down
25 changes: 25 additions & 0 deletions lib/callback-or-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
function withCallback(fn, callback) {
console.warn("Callbacks are deprecated, we're moving towards a promise-based API.");
try {
return fn().end(callback);
} catch (e) {
callback(e);
}
}

function withoutCallback(fn) {
return new Promise(function (resolve, reject) {
try {
fn().end(function (err, res) {
if (err) reject(err);
else resolve(res);
});
} catch (e) {
reject(e);
}
});
}

module.exports = function(fn, callback) {
return callback ? withCallback(fn, callback) : withoutCallback(fn);
};
75 changes: 35 additions & 40 deletions lib/remote-api-impl.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,54 @@
var superagent = require('superagent');
var callbackOrPromise = require('./callback-or-promise');

module.exports = function (options) {

return {

mock: function(opts, callback) {
superagent.post(options.baseUrl + "/syncMock")
.type("json")
.send(opts)
.end(function(err, res) {
if (callback) callback(err, res)
});
mock: function (opts, callback) {
return callbackOrPromise(function () {
return superagent.post(options.baseUrl + "/syncMock")
.type("json")
.send(opts);
}, callback);
},

mocks: function(opts, callback) {
superagent.post(options.baseUrl +"/syncMocks")
.type("json")
.send(opts)
.end(function(err, res) {
if (callback) callback(err, res)
});
mocks: function (opts, callback) {
return callbackOrPromise(function () {
return superagent.post(options.baseUrl + "/syncMocks")
.type("json")
.send(opts);
}, callback);
},

defaults: function(opts, callback) {
superagent.post(options.baseUrl +"/syncDefaults")
.type("json")
.send(opts)
.end(function(err, res) {
if (callback) callback(err, res)
});
defaults: function (opts, callback) {
return callbackOrPromise(function () {
return superagent.post(options.baseUrl + "/syncDefaults")
.type("json")
.send(opts);
}, callback);
},

lastRequest: function(method, path, callback) {
superagent.get(options.baseUrl +"/lastRequest")
.set('method', method)
.set('path', path)
.end(function(err, res) {
if (callback) callback(err, res)
});
lastRequest: function (method, path, callback) {
return callbackOrPromise(function () {
return superagent.get(options.baseUrl + "/lastRequest")
.set('method', method)
.set('path', path);
}, callback);
},

lastRequests: function(method, path, callback) {
superagent.get(options.baseUrl +"/lastRequests")
.set('method', method)
.set('path', path)
.end(function(err, res) {
if (callback) callback(err, res)
});
lastRequests: function (method, path, callback) {
return callbackOrPromise(function () {
return superagent.get(options.baseUrl + "/lastRequests")
.set('method', method)
.set('path', path);
}, callback);
},

clearLastRequests: function(callback) {
superagent.delete(options.baseUrl +"/clearLastRequests")
.end(function(err, res) {
if (callback) callback(err, res)
});
clearLastRequests: function (callback) {
return callbackOrPromise(function () {
return superagent.delete(options.baseUrl + "/clearLastRequests");
}, callback);
}

}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"devDependencies": {
"chai": "^3.0.0",
"chai-spies": "^0.7.1",
"mocha": "^2.2.5",
"nock": "^5.2.1",
"zombie": "^5.0.5"
Expand Down
117 changes: 117 additions & 0 deletions test/callback-or-promise.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
var chai = require('chai');
chai.use(require('chai-spies'));
var expect = chai.expect;

var callbackOrPromise = require('../lib/callback-or-promise');

function createSuccessfulSuperAgentAPI(result) {
return chai.spy(function () {
return {
end: function (callback) {
callback(null, result);
}
};
});
}

function createFailingSuperAgentAPI(error) {
return chai.spy(function () {
return {
end: function (callback) {
callback(error);
}
};
});
}

var success = "success";
var error = new Error("ouch!");

describe('callback-or-promise', function () {

describe('with callback', function () {

it('calls the function then on success calls callback with success', function () {
var callback = chai.spy();
var fn = createSuccessfulSuperAgentAPI(success);

callbackOrPromise(fn, callback);

expect(fn).to.have.been.called();
expect(callback).to.have.been.called.with(null, success);
});

it('calls the function then on failure calls callback with error', function () {
var callback = chai.spy();
var fn = createFailingSuperAgentAPI(error);

callbackOrPromise(fn, callback);

expect(fn).to.have.been.called();
expect(callback).to.have.been.called.with(error);
});

it('calls the function then on inner failure calls callback with error', function () {
var callback = chai.spy();
var fn = chai.spy(function() { throw error; });

callbackOrPromise(fn, callback);

expect(fn).to.have.been.called();
expect(callback).to.have.been.called.with(error);
});

it('logs a deprecation warning', function() {
var spy = chai.spy.on(console, 'warn');

callbackOrPromise(function() {}, function() {});

expect(spy).to.have.been.called.with("Callbacks are deprecated, we're moving towards a promise-based API.");
});

});

describe('without callback', function () {

it('calls the function then on success returns a promise that will resolve', function (done) {
var fn = createSuccessfulSuperAgentAPI(success);

callbackOrPromise(fn).then(function(result) {
expect(fn).to.have.been.called();
expect(result).to.eq(success);
done();
});

});

it('calls the function then on failure returns a promise that will be rejected', function (done) {
var fn = createFailingSuperAgentAPI(error);

callbackOrPromise(fn).catch(function(error) {
expect(fn).to.have.been.called();
expect(error).to.eq(error);
done();
});

});

it('calls the function then on inner failure returns a promise that will be rejected', function (done) {
var fn = chai.spy(function() { throw error; });

callbackOrPromise(fn).catch(function(error) {
expect(fn).to.have.been.called();
expect(error).to.eq(error);
done();
});
});

it('does not log a deprecation warning', function() {
var spy = chai.spy.on(console, 'warn');

callbackOrPromise(function() {}, function() {});

expect(spy).to.have.not.been.called;
});
});

});
10 changes: 5 additions & 5 deletions test/remote-api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe("Remote API", function() {

var scope = nock('http://localhost:7001').post('/syncMock', defaultsData).reply(200);

api.mock(defaultsData, function() {
api.mock(defaultsData).then(function() {
scope.done();
done();
});
Expand All @@ -24,7 +24,7 @@ describe("Remote API", function() {

var scope = nock('http://localhost:7001').post('/syncMocks', mocksData).reply(200);

api.mocks(mocksData, function() {
api.mocks(mocksData).then(function() {
scope.done();
done();
});
Expand All @@ -35,7 +35,7 @@ describe("Remote API", function() {

var scope = nock('http://localhost:7001').post('/syncDefaults', defaultsData).reply(200);

api.defaults(defaultsData, function() {
api.defaults(defaultsData).then(function() {
scope.done();
done();
});
Expand All @@ -46,7 +46,7 @@ describe("Remote API", function() {

var scope = nock('http://localhost:7001', { method: "POST", path: "/" }).get('/lastRequest').reply(204, lastRequestData);

api.lastRequest("POST", "/", function(err, res) {
api.lastRequest("POST", "/").then(function(res) {
expect(res.body).to.deep.eq(lastRequestData);
scope.done();
done();
Expand All @@ -58,7 +58,7 @@ describe("Remote API", function() {

var scope = nock('http://localhost:7001', { method: 'POST', path: '/' }).get('/lastRequests').reply(204, lastRequestsData);

api.lastRequests("POST", "/", function(err, res) {
api.lastRequests("POST", "/").then(function(res) {
expect(res.body).to.deep.eq(lastRequestsData);
scope.done();
done();
Expand Down

0 comments on commit b4b73fd

Please sign in to comment.