Created
March 17, 2015 11:27
-
-
Save mik01aj/0e7fd661ef0ac8917181 to your computer and use it in GitHub Desktop.
ModelStore - A simple tool to gather data requests from frontend to batch queries. See 2nd unit test for usage example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*jshint jasmine: true */ | |
'use strict'; | |
var ModelStore = require('../ModelStore'); | |
var Promise = require('es6-promise').Promise; | |
var _ = require('lodash'); | |
describe('ModelStore', function () { | |
var mockFunctionForTheThingApi, axiosGetSpy, store; | |
beforeEach(function () { | |
mockFunctionForTheThingApi = function(ids) { | |
return { | |
status: 200, | |
data: { | |
things: _.map(ids, function (id) { | |
return {id: id, hello: 'world'}; | |
}), | |
}, | |
}; | |
}; | |
// This is the fake function to handle HTTP requests | |
axiosGetSpy = jasmine.createSpy('axios.get').and.callFake(function (url) { | |
var ids = _.map(url.split('/things/')[1].split(','), _.parseInt); | |
return new Promise(function(fulfill, reject) { | |
fulfill(mockFunctionForTheThingApi(ids)); | |
}); | |
}); | |
// Mocking stuff. | |
ModelStore.__set__('console.debug', function() {}); // disabling some console output | |
ModelStore.__set__('console.warn', function() {}); // disabling some console output | |
ModelStore.__set__('console.assert', function(condition, message) { | |
message = message || 'Assertion failed'; | |
expect(condition).toBeTruthy(message); | |
}); // disabling console.logs | |
ModelStore.__set__('axios.get', axiosGetSpy); // mocking HTTP communication | |
// And this is the ModelStore we're going to test! | |
store = new ModelStore('http://example.com/things', 'things'); | |
jasmine.clock().install(); | |
}); | |
afterEach(function () { | |
jasmine.clock().uninstall(); | |
}); | |
it('should resolve the promise when it receives a response', function () { | |
var promiseResolved = false; | |
store.get(123).then(function (thing) { | |
promiseResolved = true; | |
expect(thing).toEqual({id: 123, hello: 'world'}); | |
}).catch(fail); | |
jasmine.clock().tick(1); | |
expect(promiseResolved).toBe(true); | |
}); | |
it('should group requests for multiple ids', function () { | |
store.get(1); | |
store.get(2); | |
jasmine.clock().tick(1); | |
expect(axiosGetSpy).toHaveBeenCalledWith('http://example.com/things/1,2'); | |
store.get(3); | |
store.get(4); | |
store.get(5); | |
jasmine.clock().tick(1); | |
expect(axiosGetSpy).toHaveBeenCalledWith('http://example.com/things/3,4,5'); | |
}); | |
it('should not request the same thing again', function () { | |
store.get(123); | |
jasmine.clock().tick(1); | |
expect(axiosGetSpy.calls.count()).toBe(1); | |
store.get(123); | |
jasmine.clock().tick(1); | |
expect(axiosGetSpy.calls.count()).toBe(1); | |
}); | |
it('should not rely on the ordering in the result', function () { | |
mockFunctionForTheThingApi = function(ids) { | |
expect(ids).toEqual([1, 2]); | |
return { | |
status: 200, | |
data: { | |
things: [ | |
{id: 2, hello: 'world'}, | |
{id: 1, hello: 'world'}, | |
], | |
}, | |
}; | |
}; | |
var doneCount = 0; | |
store.get(1).then(function (result) { | |
expect(result).toEqual({id: 1, hello: 'world'}); | |
doneCount++; | |
}); | |
store.get(2).then(function (result) { | |
expect(result).toEqual({id: 2, hello: 'world'}); | |
doneCount++; | |
}); | |
jasmine.clock().tick(1); | |
expect(doneCount).toBe(2); | |
}); | |
it('should handle missing results nicely', function () { | |
mockFunctionForTheThingApi = function(ids) { | |
expect(ids).toEqual([1, 2]); | |
return { | |
status: 200, | |
data: { | |
things: [ | |
{id: 1, hello: 'world'}, | |
], | |
}, | |
}; | |
}; | |
var doneCount = 0; | |
store.get(1).then(function (result) { | |
expect(result).toEqual({id: 1, hello: 'world'}); | |
doneCount++; | |
}); | |
store.get(2).catch(function (err) { | |
expect(err).not.toBeNull(); | |
doneCount++; | |
}); | |
jasmine.clock().tick(1); | |
expect(doneCount).toBe(2); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var axios = require('axios'); | |
var Promise = require('es6-promise').Promise; | |
var _ = require('lodash'); | |
var ModelStore = function(endpointUrl, endpointType) { | |
this.endpointUrl = endpointUrl; | |
this.endpointType = endpointType; | |
this.store = {}; | |
this.requested = {}; | |
this.requestTimeout = null; | |
}; | |
ModelStore.prototype.get = function(id) { | |
var that = this; | |
return new Promise(function (fulfill, reject) { | |
if (that.store[id]) { | |
fulfill(that.store[id]); | |
} else { | |
that._addRequest(id, fulfill, reject); | |
} | |
}); | |
}; | |
ModelStore.prototype._addRequest = function(id, fulfillCallback, rejectCallback) { | |
console.assert(fulfillCallback != null); | |
if (!this.requested[id]) { | |
this.requested[id] = []; | |
} | |
this.requested[id].push({fulfill: fulfillCallback, reject: rejectCallback}); | |
if (!this.requestTimeout) { | |
this.requestTimeout = setTimeout(this.fetchRequested.bind(this), 0); | |
} | |
}; | |
ModelStore.prototype.fetchRequested = function() { | |
this.requestTimeout = null; | |
var that = this; | |
var requested = this.requested; | |
this.requested = {}; | |
var ids = _.keys(requested); | |
var url = this.endpointUrl + '/' + ids.join(','); | |
return axios.get(url).then(function(response) { | |
console.debug('ModelStore: ' + url + ' --> ', response); | |
var responseObjects = response.data[that.endpointType]; | |
console.assert(responseObjects); | |
// fulfilling promises that could be fulfilled | |
_.each(responseObjects, function (receivedObj) { | |
console.assert(receivedObj); | |
var id = receivedObj.id; | |
console.assert(id in requested, | |
'Unexpected object with id=' + id + ': requested ' + ids); | |
that.store[id] = receivedObj; // Saving to cache | |
_.each(requested[id], function(promiseCallbacks) { | |
promiseCallbacks.fulfill(receivedObj); | |
}); | |
delete requested[id]; | |
}); | |
// Sadly rejecting promises that could not be fulfilled | |
_.each(requested, function(promiseCallbacksList, id) { | |
_.each(promiseCallbacksList, function(promiseCallbacks) { | |
var message = ('The ' + that.endpointType + ' with id ' + id + | |
' was not present in the response at ' + url); | |
console.warn(message); | |
promiseCallbacks.reject(message); | |
}); | |
}); | |
}).catch(function (err) { | |
console.error('ModelStore: ' + url + ' --> ', err); | |
// TODO: if error is recoverable, retry the request | |
}); | |
}; | |
module.exports = ModelStore; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment