From f70cba211d5cbebe282990bccd41111a9e7c9717 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 10 May 2015 20:03:22 +0100 Subject: [PATCH 1/4] ability to export mock function rather than stubbing global fethc --- src/fetch-mock.js | 65 +++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/fetch-mock.js b/src/fetch-mock.js index 547a8da2..1d024266 100644 --- a/src/fetch-mock.js +++ b/src/fetch-mock.js @@ -99,6 +99,12 @@ var FetchMock = function (opts) { theGlobal = opts.theGlobal; this.routes = []; this._calls = {}; + this.stubGlobalFetch = true; +}; + +FetchMock.prototype.setNonGlobalFetch = function (func) { + this.stubGlobalFetch = false; + this.fetch = func; }; FetchMock.prototype.registerRoute = function (name, matcher, response) { @@ -145,6 +151,7 @@ FetchMock.prototype.unregisterRoute = function (names) { FetchMock.prototype.getRouter = function (config) { debug('building router'); + var routes; if (config.routes) { @@ -219,45 +226,59 @@ FetchMock.prototype.push = function (name, call) { FetchMock.prototype.mock = function (config) { debug('mocking fetch'); - var self = this; + if (this.isMocking) { throw 'fetch-mock is already mocking routes. Call .restore() before mocking again or use .reMock() if this is intentional'; } this.isMocking = true; + var mock = this.constructMock(config); + + + if (this.stubGlobalFetch) { + debug('applying sinon.stub to fetch'); + sinon.stub(theGlobal, 'fetch', mock); + } else { + return mock; + } +}; + +FetchMock.prototype.constructMock = function (config) { config = config || {}; - var defaultFetch = theGlobal.fetch; + var self = this; + var defaultFetch = this.stubGlobalFetch ? theGlobal.fetch : this.fetch; var router = this.getRouter(config); config.greed = config.greed || 'none'; - debug('applying sinon.stub to fetch') - sinon.stub(theGlobal, 'fetch', function (url, opts) { - var response = router(url, opts); - if (response) { - debug('response found for ' + url); - return mockResponse(url, response); + return function (url, opts) { + var response = router(url, opts); + if (response) { + debug('response found for ' + url); + return mockResponse(url, response); + } else { + debug('response not found for ' + url); + self.push('__unmatched', [url, opts]); + if (config.greed === 'good') { + debug('sending default good response'); + return mockResponse(url, {body: 'unmocked url: ' + url}); + } else if (config.greed === 'bad') { + debug('sending default bad response'); + return mockResponse(url, {throws: 'unmocked url: ' + url}); } else { - debug('response not found for ' + url); - self.push('__unmatched', [url, opts]); - if (config.greed === 'good') { - debug('sending default good response'); - return mockResponse(url, {body: 'unmocked url: ' + url}); - } else if (config.greed === 'bad') { - debug('sending default bad response'); - return mockResponse(url, {throws: 'unmocked url: ' + url}); - } else { - debug('forwarding to default fetch'); - return defaultFetch(url, opts); - } + debug('forwarding to default fetch'); + return defaultFetch(url, opts); } - }); + } + } }; FetchMock.prototype.restore = function () { debug('restoring fetch'); this.isMocking = false; this.reset(); - theGlobal.fetch.restore(); + if (this.stubGlobalFetch) { + theGlobal.fetch.restore(); + } debug('fetch restored'); }; From ef89c991bb5dbb76842d614c3404e4a5197f60e5 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 10 May 2015 20:24:53 +0100 Subject: [PATCH 2/4] documenting mckery use --- README.md | 25 ++++++++++++++++++++++++- src/fetch-mock.js | 7 +++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ec7106a3..8271df16 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Mock http requests made using fetch (or isomorphic-fetch) *notes* -- When using isomorphic-fetch or node-fetch `fetch` should be added as a global +- When using isomorphic-fetch or node-fetch ideally `fetch` should be added as a global. If not possible to do so you can still use fetch-mock in combination with [mockery](https://github.com/mfncooper/mockery) in nodejs (see `setNonGlobalFetch(func)` below) - fetch-mock doesn't declare `fetch` or `Promise` as dependencies; as you're testing `fetch` it's assumed you're already taking care of these globals - fetch-mock uses [npm debug](https://www.npmjs.com/package/debug). To output useful messages for debugging set the environment variable `DEBUG=fetch-mock` - If you prefer documentation by example skip to the bottom of this README @@ -81,6 +81,29 @@ Often your application/module will need a mocked response for some http requests ### `unregisterRoute(name)` Unregisters one or more previously registered routes. Accepts either a string or an array of strings +### `useNonGlobalFetch(func)` +To use fetch-mock with with [mockery](https://github.com/mfncooper/mockery) you will need to use this function to prevent fetch-mock trying to mock the function globally. +* `func` Optional reference to `fetch` (or any other function you may want to substitute for `fetch` in your tests). This will be converted to a `sinon.stub` and can be accessed via `fetchMock.fetch` + +#### Mockery example +```javascript +var fetch = require('node-fetch'); +var fetchMock = require('fetch-mock'); +var mockery = require('mockery'); +fetchMock.useNonGlobalFetch(fetch); + +fetchMock.registerRoute([ + ... +]) +it('should make a request', function (done) { + mockery.registerMock('fetch', fetchMock.mock()); + // test code goes in here + mockery.deregisterMock('fetch'); + done(); +}); + +``` + ## Example ```javascript diff --git a/src/fetch-mock.js b/src/fetch-mock.js index 1d024266..a439085a 100644 --- a/src/fetch-mock.js +++ b/src/fetch-mock.js @@ -102,7 +102,7 @@ var FetchMock = function (opts) { this.stubGlobalFetch = true; }; -FetchMock.prototype.setNonGlobalFetch = function (func) { +FetchMock.prototype.useNonGlobalFetch = function (func) { this.stubGlobalFetch = false; this.fetch = func; }; @@ -239,6 +239,7 @@ FetchMock.prototype.mock = function (config) { debug('applying sinon.stub to fetch'); sinon.stub(theGlobal, 'fetch', mock); } else { + this.fetch && sinon.stub(this, 'fetch', mock); return mock; } }; @@ -266,7 +267,7 @@ FetchMock.prototype.constructMock = function (config) { return mockResponse(url, {throws: 'unmocked url: ' + url}); } else { debug('forwarding to default fetch'); - return defaultFetch(url, opts); + return defaultFetch && defaultFetch(url, opts); } } } @@ -278,6 +279,8 @@ FetchMock.prototype.restore = function () { this.reset(); if (this.stubGlobalFetch) { theGlobal.fetch.restore(); + } else if (this.fetch) { + this.fetch.restore(); } debug('fetch restored'); }; From b1a2704c3ec0f3f01dc0a1119aa9f8ea033fb1b1 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 10 May 2015 20:46:56 +0100 Subject: [PATCH 3/4] tests for non-global use --- src/fetch-mock.js | 16 +++++++----- test/server.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/fetch-mock.js b/src/fetch-mock.js index a439085a..6d878a8e 100644 --- a/src/fetch-mock.js +++ b/src/fetch-mock.js @@ -99,11 +99,11 @@ var FetchMock = function (opts) { theGlobal = opts.theGlobal; this.routes = []; this._calls = {}; - this.stubGlobalFetch = true; + this.usesGlobalFetch = true; }; FetchMock.prototype.useNonGlobalFetch = function (func) { - this.stubGlobalFetch = false; + this.usesGlobalFetch = false; this.fetch = func; }; @@ -235,7 +235,7 @@ FetchMock.prototype.mock = function (config) { var mock = this.constructMock(config); - if (this.stubGlobalFetch) { + if (this.usesGlobalFetch) { debug('applying sinon.stub to fetch'); sinon.stub(theGlobal, 'fetch', mock); } else { @@ -247,7 +247,7 @@ FetchMock.prototype.mock = function (config) { FetchMock.prototype.constructMock = function (config) { config = config || {}; var self = this; - var defaultFetch = this.stubGlobalFetch ? theGlobal.fetch : this.fetch; + var defaultFetch = this.usesGlobalFetch ? theGlobal.fetch : this.fetch; var router = this.getRouter(config); config.greed = config.greed || 'none'; @@ -277,7 +277,7 @@ FetchMock.prototype.restore = function () { debug('restoring fetch'); this.isMocking = false; this.reset(); - if (this.stubGlobalFetch) { + if (this.usesGlobalFetch) { theGlobal.fetch.restore(); } else if (this.fetch) { this.fetch.restore(); @@ -293,7 +293,11 @@ FetchMock.prototype.reMock = function (config) { FetchMock.prototype.reset = function () { debug('resetting call logs'); this._calls = {}; - theGlobal.fetch.reset(); + if (this.usesGlobalFetch) { + theGlobal.fetch.reset(); + } else if (this.fetch) { + this.fetch.reset(); + } debug('call logs reset'); }; diff --git a/test/server.js b/test/server.js index 7c015b2f..e41ddf83 100644 --- a/test/server.js +++ b/test/server.js @@ -3,5 +3,66 @@ require('es6-promise').polyfill(); var fetchMock = require('../server.js'); +var fetchCalls = []; +var expect = require('chai').expect; +var sinon = require('sinon'); + +// we can't use sinon to spy on fetch in these tests as fetch-mock +// uses it internally and sinon doesn't allow spying on a previously +// stubbed function, so just use this very basic stub +var dummyFetch = function () { + fetchCalls.push([].slice.call(arguments)); + return Promise.resolve(arguments); +}; + +var err = function (err) { + console.log(error); +} + +require('./spec')(fetchMock, GLOBAL); + +describe('non-global use', function () { + + before(function () { + try { + fetchMock.restore(); + } catch (e) {} + }); + + it('returns mock function when non global fetch used', function () { + fetchMock.useNonGlobalFetch(); + var mock = fetchMock.mock(); + expect(typeof mock).to.equal('function'); + expect(typeof fetchMock.fetch).to.equal('undefined'); + expect(function () { + mock('url', {prop: 'val'}) + }).not.to.throw(); + expect(fetchMock.called('__unmatched')).to.be.true; + fetchMock.restore(); + fetchMock.usesGlobalFetch = true; + }); + + it('stubs non global fetch if function passed in', function () { + fetch('url', {prop: 'val'}); + + fetchMock.useNonGlobalFetch(dummyFetch); + expect(fetchMock.fetch).to.equal(dummyFetch); + var mock = fetchMock.mock(); + expect(typeof mock).to.equal('function'); + expect(typeof fetchMock.fetch.called).to.equal('boolean'); + expect(function () { + mock('url', {prop: 'val'}) + }).not.to.throw(); + expect(fetchMock.called('__unmatched')).to.be.true; + expect(fetchCalls.length).to.equal(1); + expect(fetchCalls[0]).to.eql(['url', {prop: 'val'}]); + + fetchMock.restore(); + fetchMock.usesGlobalFetch = true; + }); +}); + + + + -require('./spec')(fetchMock, GLOBAL); \ No newline at end of file From f2756e9d131b804f5e79d94f180f10ae8acafef0 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 10 May 2015 20:50:52 +0100 Subject: [PATCH 4/4] updating debug info --- README.md | 2 +- src/fetch-mock.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8271df16..1cc982ad 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Mock http requests made using fetch (or isomorphic-fetch) *notes* -- When using isomorphic-fetch or node-fetch ideally `fetch` should be added as a global. If not possible to do so you can still use fetch-mock in combination with [mockery](https://github.com/mfncooper/mockery) in nodejs (see `setNonGlobalFetch(func)` below) +- When using isomorphic-fetch or node-fetch ideally `fetch` should be added as a global. If not possible to do so you can still use fetch-mock in combination with [mockery](https://github.com/mfncooper/mockery) in nodejs (see `useNonGlobalFetch(func)` below) - fetch-mock doesn't declare `fetch` or `Promise` as dependencies; as you're testing `fetch` it's assumed you're already taking care of these globals - fetch-mock uses [npm debug](https://www.npmjs.com/package/debug). To output useful messages for debugging set the environment variable `DEBUG=fetch-mock` - If you prefer documentation by example skip to the bottom of this README diff --git a/src/fetch-mock.js b/src/fetch-mock.js index 6d878a8e..42e55202 100644 --- a/src/fetch-mock.js +++ b/src/fetch-mock.js @@ -234,17 +234,20 @@ FetchMock.prototype.mock = function (config) { this.isMocking = true; var mock = this.constructMock(config); - if (this.usesGlobalFetch) { debug('applying sinon.stub to fetch'); sinon.stub(theGlobal, 'fetch', mock); } else { - this.fetch && sinon.stub(this, 'fetch', mock); + if (this.fetch) { + debug('applying sinon.stub to fetch'); + sinon.stub(this, 'fetch', mock); + } return mock; } }; FetchMock.prototype.constructMock = function (config) { + debug('constructing mock function'); config = config || {}; var self = this; var defaultFetch = this.usesGlobalFetch ? theGlobal.fetch : this.fetch;