each resource file, say api/enamel/1.0/infoset.js
, defines an ancestor of the
Model
class. the resource class has a __requests__
member that defines
mesh/request
instances for each request (get, put, post, query, delete, etc).
each of these request instances has an ajax
method that's just a reference to
$.ajax
, and it uses that do actually make the xhr.
'mocking' a resource amounts to the following:
- defining a set of fixtures, a.k.a. an array of objects with fields corresponding to the given resource
- swapping out the 'ajax' method of each of the resource's requests to respond with data from the fixtures (no xhr is ever made)
keep in mind we're monkey-patching the resource. so say we've got a unit test
file at src/foo/test.js
, and it requires the infoset-mocking file
auxl/test/mock/infoset.js
. then every other module using infosets get the
mocked version, even if they required the real resource at
api/enamel/1.0/infoset.js
. this is good because you don't have to modify the
real code to create a test environment, and you can just define the resources
you'd like to mock in the test file.
say we'd like to mock the 'matter' resource that lives at:
api/docket.document/1.0/siq.matter.js
we'll put the mocked resource in auxl:
auxl/test/mock/matter.js
start by defining fixtures in JSON at:
auxl/test/mock/matterfixtures.json
i usually get the fixture list by querying the api from the chrome dev tools
(with like a Matter.collection().load()
), and then copy/pasting the result
from the 'network' tab. the result looks something like this:
[
{
"defunct": false,
"name": "second ever mocked",
"designation": null,
"created": "2013-03-06T14:08:00Z",
"modified": "2013-03-06T14:10:39Z",
"id": "458f4360-8f20-4c00-9391-6a92897aca67",
"description": null
},
{
"defunct": false,
"name": "third ever mocked",
"designation": null,
"created": "2013-03-06T22:11:09Z",
"modified": "2013-03-06T22:11:09Z",
"id": "bea8ba82-2d1c-4b18-ac2b-0a0f82d3dd53",
"description": "homie we major"
},
// ... etc ...
alternatively, if the api is not available, or you don't have any defined, you
may want to just write like a _.map()
function to go through and spit out a
list of these objects. some of the example fixtures are actually javascript
that generates the data on the fly at page load. either way just paste the
results into the *.json file.
next we'll define the mocked resource that intercepts ajax
calls. define a
module in auxl/test/mock/matter.js
that looks like this:
define([
'mesh/tests/mockutils',
'api/docket.document/1.0/siq.matter',
'text!./matterfixtures.json'
], function(mock, Matter, fixturesJson) {
var fixtures = JSON.parse(fixturesJson);
return mock('matter', Matter, fixtures);
});
breaking it down by lines:
- line 2: the
mesh/tests/mockutils
module returns a function that does all of the boilerplate associated with swapping out those 'ajax' methods on the requests. more on that in a second. - line 3: the real resource, of course
- line 4: our fixtures file we defined in step 1
- line 7: this is where the magic happens
- 1st arg: the mocked resource is attached to
window
at this name, since it's really convenient to be able to saywindow.matter
in testing situations - 2nd arg: the resource that you want to be mocked
- 3rd arg: the fixtures -- this should be an array
- return value: the same resource, just for convenience since we're monkey
patching, it's
===
toMatter
- 1st arg: the mocked resource is attached to
so your test file will look something like this:
/*global test, asyncTest, ok, equal, deepEqual, start, module, strictEqual, notStrictEqual, raises*/
define([
'./../foo',
'auxl/test/mock/matter',
'./../../styles'
], function(Foo) {
asyncTest('instantiating a Foo', function() {
var f = Foo().appendTo('body');
ok(true);
start();
});
start();
});
and assuming that Foo
loads a list of matters, you'll be pulling from the
fixture data.
note that mesh/tests/mockutils
provides a lot, including:
- a way to specify the delay before responses:
// out-of-order load
var collection = Resource.collection();
Resource.mockDelay(50); // in millisecods
var load1 = collection.load();
Resource.mockDelay(0);
var load2 = collection.load({refresh: true});
// load2 will resolve before load1
- a way to mock failures:
var m1 = Resource.models.get(0);
m1.load(); // works
Resource.mockFailure(true);
var m2 = Resource.models.get(1);
m2.load(); // fails
Resource.mockFailure();
var m3 = Resource.models.get(2);
m3.load(); // works
- a more powerful means to intercept requests:
var reqs = [];
Resource.mockWrapRequestHandler('update', function(f, params) {
var args = Array.prototype.slice.call(arguments, 1);
reqs.push(JSON.parse(params.data));
return f.apply(this, args);
});
var m = Resource.models.get(0);
m.set('foo', 'bar');
m.save();
// the params for the request are now available on the 'reqs' var
- a means of changing the fixtures that the resource pulls from, so you can simulate a database update on the backend:
var myModel = Resource.models.get(0);
Resource.mockDataChange(function(exampleFixtures) {
var cur = _.find(exampleFixtures, function(f) {
return f.id === myModel.get('id');
});
cur.required_field = 'changed value';
});
however if you use any of the above functionality, you'll want to reset it to the original value at the beginning of the next test (this should definitely be a library function):
Resource
.mockDelay()
.mockFailure()
.mockDataChange()
.mockUnwrapRequestHandlers()
.models.clear();
- the mocked matter resource is in auxl and it's used in the
DocList
unit tests - the model unit tests have a lot of (complex) examples of using the
mesh/tests/mockutils
module
The library method
mockReset
as been added tomesh/test/mockutils
. This handles the above mentioned case for:An example of it's use can be found in
mesh/test/test_model_consistency
.