From 08f2485f01248ef275a3b4e6f194a9982c9b2479 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 23 Sep 2018 16:29:48 +0100 Subject: [PATCH 1/2] reducing duplicate code --- src/lib/compile-route.js | 1 + src/lib/fetch-handler.js | 28 ++++--- test/specs/inspecting.test.js | 33 ++------ test/specs/set-up-and-tear-down.test.js | 100 +++++++++--------------- 4 files changed, 62 insertions(+), 100 deletions(-) diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 1fc39103..e9b95b9e 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -71,6 +71,7 @@ const getParamsMatcher = ({ params: expectedParams, matcher }) => { const getUrlMatcher = route => { const { matcher, query } = route; + if (typeof matcher === 'function') { return matcher; } diff --git a/src/lib/fetch-handler.js b/src/lib/fetch-handler.js index 435ae876..2df1cd34 100644 --- a/src/lib/fetch-handler.js +++ b/src/lib/fetch-handler.js @@ -36,6 +36,21 @@ const normalizeRequest = (url, options, Request) => { } }; +const resolve = async (response, url, opts) => { + while ( + typeof response === 'function' || + typeof response.then === 'function' + ) { + if (typeof response === 'function') { + response = response(url, opts); + } else { + // Strange .then is to cope with non ES Promises... god knows why it works + response = await response.then(it => it); + } + } + return response; +} + FetchMock.fetchHandler = function(url, opts, request) { ({ url, opts, request } = normalizeRequest(url, opts, this.config.Request)); @@ -96,18 +111,7 @@ FetchMock.generateResponse = async function(route, url, opts) { // Because of this we can't safely check for function before Promisey-ness, // or vice versa. So to keep it DRY, and flexible, we keep trying until we // have something that looks like neither Promise nor function - let response = route.response; - while ( - typeof response === 'function' || - typeof response.then === 'function' - ) { - if (typeof response === 'function') { - response = response(url, opts); - } else { - // Strange .then is to cope with non ES Promises... god knows why it works - response = await response.then(it => it); - } - } + let response = await resolve(route.response, url, opts); // If the response says to throw an error, throw it // Type checking is to deal with sinon spies having a throws property :-0 diff --git a/test/specs/inspecting.test.js b/test/specs/inspecting.test.js index ad73d61c..40fe471f 100644 --- a/test/specs/inspecting.test.js +++ b/test/specs/inspecting.test.js @@ -66,31 +66,14 @@ module.exports = fetchMock => { afterEach(() => { fm.filterCalls.restore(); }); - it('called() uses the internal filtering method', () => { - fm.called('name', { an: 'option' }); - expect(fm.filterCalls.calledWith('name', { an: 'option' })).to.be - .true; - }); - it('calls() uses the internal filtering method', () => { - fm.calls('name', { an: 'option' }); - expect(fm.filterCalls.calledWith('name', { an: 'option' })).to.be - .true; - }); - it('lastCall() uses the internal filtering method', () => { - fm.lastCall('name', { an: 'option' }); - expect(fm.filterCalls.calledWith('name', { an: 'option' })).to.be - .true; - }); - it('lastUrl() uses the internal filtering method', () => { - fm.lastUrl('name', { an: 'option' }); - expect(fm.filterCalls.calledWith('name', { an: 'option' })).to.be - .true; - }); - it('lastOptions() uses the internal filtering method', () => { - fm.lastOptions('name', { an: 'option' }); - expect(fm.filterCalls.calledWith('name', { an: 'option' })).to.be - .true; - }); + ['called', 'calls', 'lastCall', 'lastUrl', 'lastOptions'] + .forEach(method => { + it(`${method}() uses the internal filtering method`, () => { + fm[method]('name', { an: 'option' }); + expect(fm.filterCalls.calledWith('name', { an: 'option' })).to.be + .true; + }); + }) }); }); diff --git a/test/specs/set-up-and-tear-down.test.js b/test/specs/set-up-and-tear-down.test.js index 9d4a88d1..ad00f995 100644 --- a/test/specs/set-up-and-tear-down.test.js +++ b/test/specs/set-up-and-tear-down.test.js @@ -3,6 +3,19 @@ chai.use(require('sinon-chai')); const expect = chai.expect; const sinon = require('sinon'); +const testChainableMethod = (getFetchMock, method, args = []) => { + it('is chainable', () => { + expect(getFetchMock()[method](...args)).to.equal(getFetchMock()); + }); + + it("has 'this'", () => { + sinon.spy(getFetchMock(), method); + getFetchMock()[method](...args); + expect(getFetchMock()[method].lastCall.thisValue).to.equal(getFetchMock()); + getFetchMock()[method].restore(); + }); +} + module.exports = fetchMock => { describe('Set up and tear down', () => { let fm; @@ -13,16 +26,7 @@ module.exports = fetchMock => { afterEach(() => fm.restore()); describe('mock', () => { - it('is chainable', () => { - expect(fm.mock(/a/, 200)).to.equal(fm); - }); - - it("has 'this'", () => { - sinon.spy(fm, 'mock'); - fm.mock(/a/, 200); - expect(fm.mock.lastCall.thisValue).to.equal(fm); - fm.mock.restore(); - }); + testChainableMethod(() => fm, 'mock', [/a/, 200]); it('can be called multiple times', () => { expect(() => { @@ -97,6 +101,8 @@ module.exports = fetchMock => { describe('method shorthands', () => { 'get,post,put,delete,head,patch'.split(',').forEach(method => { + testChainableMethod(() => fm, method, [/a/, 200]); + it(`has shorthand for ${method.toUpperCase()}`, () => { sinon.stub(fm, 'mock'); fm[method]('a', 'b'); @@ -112,25 +118,29 @@ module.exports = fetchMock => { fm.restore(); }); - it(`shorthand for ${method.toUpperCase()} is chainable`, () => { - const result = fm[method]('a', 'b'); - expect(result).to.equal(fm); + testChainableMethod(() => fm, `${method}Once`, [/a/, 200]); + + it(`has shorthand for ${method.toUpperCase()} called once`, () => { + sinon.stub(fm, 'mock'); + fm[`${method}Once`]('a', 'b'); + fm[`${method}Once`]('a', 'b', { opt: 'c' }); + expect(fm.mock).calledWith('a', 'b', { + method: method.toUpperCase(), + repeat: 1 + }); + expect(fm.mock).calledWith('a', 'b', { + opt: 'c', + method: method.toUpperCase(), + repeat: 1 + }); + fm.mock.restore(); fm.restore(); }); }); }); describe('reset', () => { - it('is chainable', () => { - expect(fm.restore()).to.equal(fm); - }); - - it("has 'this'", () => { - sinon.spy(fm, 'restore'); - fm.restore(); - expect(fm.restore.lastCall.thisValue).to.equal(fm); - fm.restore.restore(); - }); + testChainableMethod(() => fm, 'reset'); it('can be called even if no mocks set', () => { expect(() => fm.restore()).not.to.throw(); @@ -161,16 +171,7 @@ module.exports = fetchMock => { }); describe('resetBehavior', () => { - it('is chainable', () => { - expect(fm.resetBehavior()).to.equal(fm); - }); - - it("has 'this'", () => { - sinon.spy(fm, 'resetBehavior'); - fm.resetBehavior(); - expect(fm.resetBehavior.lastCall.thisValue).to.equal(fm); - fm.resetBehavior.restore(); - }); + testChainableMethod(() => fm, 'resetBehavior'); it('can be called even if no mocks set', () => { expect(() => fm.resetBehavior()).not.to.throw(); @@ -190,16 +191,7 @@ module.exports = fetchMock => { }); describe('resetHistory', () => { - it('is chainable', () => { - expect(fm.resetHistory()).to.equal(fm); - }); - - it("has 'this'", () => { - sinon.spy(fm, 'resetHistory'); - fm.resetHistory(); - expect(fm.resetHistory.lastCall.thisValue).to.equal(fm); - fm.resetHistory.restore(); - }); + testChainableMethod(() => fm, 'resetHistory'); it('can be called even if no mocks set', () => { expect(() => fm.resetHistory()).not.to.throw(); @@ -222,16 +214,7 @@ module.exports = fetchMock => { }); describe('spy', () => { - it('is chainable', () => { - expect(fm.spy()).to.equal(fm); - }); - - it("has 'this'", () => { - sinon.spy(fm, 'spy'); - fm.spy(); - expect(fm.spy.lastCall.thisValue).to.equal(fm); - fm.spy.restore(); - }); + testChainableMethod(() => fm, 'spy'); it('calls catch()', () => { sinon.spy(fm, 'catch'); @@ -242,16 +225,7 @@ module.exports = fetchMock => { }); describe('catch', () => { - it('is chainable', () => { - expect(fm.catch(200)).to.equal(fm); - }); - - it("has 'this'", () => { - sinon.spy(fm, 'catch'); - fm.catch(200); - expect(fm.catch.lastCall.thisValue).to.equal(fm); - fm.catch.restore(); - }); + testChainableMethod(() => fm, 'catch'); }); }); }; From 7b7e6da5dc912536070ef17ef37fe6a39f8fc818 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 23 Sep 2018 16:30:04 +0100 Subject: [PATCH 2/2] lint --- src/lib/fetch-handler.js | 4 ++-- test/specs/inspecting.test.js | 7 ++++--- test/specs/set-up-and-tear-down.test.js | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/fetch-handler.js b/src/lib/fetch-handler.js index 2df1cd34..1d8d4fb7 100644 --- a/src/lib/fetch-handler.js +++ b/src/lib/fetch-handler.js @@ -49,7 +49,7 @@ const resolve = async (response, url, opts) => { } } return response; -} +}; FetchMock.fetchHandler = function(url, opts, request) { ({ url, opts, request } = normalizeRequest(url, opts, this.config.Request)); @@ -111,7 +111,7 @@ FetchMock.generateResponse = async function(route, url, opts) { // Because of this we can't safely check for function before Promisey-ness, // or vice versa. So to keep it DRY, and flexible, we keep trying until we // have something that looks like neither Promise nor function - let response = await resolve(route.response, url, opts); + const response = await resolve(route.response, url, opts); // If the response says to throw an error, throw it // Type checking is to deal with sinon spies having a throws property :-0 diff --git a/test/specs/inspecting.test.js b/test/specs/inspecting.test.js index 40fe471f..fc6978c2 100644 --- a/test/specs/inspecting.test.js +++ b/test/specs/inspecting.test.js @@ -66,14 +66,15 @@ module.exports = fetchMock => { afterEach(() => { fm.filterCalls.restore(); }); - ['called', 'calls', 'lastCall', 'lastUrl', 'lastOptions'] - .forEach(method => { + ['called', 'calls', 'lastCall', 'lastUrl', 'lastOptions'].forEach( + method => { it(`${method}() uses the internal filtering method`, () => { fm[method]('name', { an: 'option' }); expect(fm.filterCalls.calledWith('name', { an: 'option' })).to.be .true; }); - }) + } + ); }); }); diff --git a/test/specs/set-up-and-tear-down.test.js b/test/specs/set-up-and-tear-down.test.js index ad00f995..a1323edb 100644 --- a/test/specs/set-up-and-tear-down.test.js +++ b/test/specs/set-up-and-tear-down.test.js @@ -14,7 +14,7 @@ const testChainableMethod = (getFetchMock, method, args = []) => { expect(getFetchMock()[method].lastCall.thisValue).to.equal(getFetchMock()); getFetchMock()[method].restore(); }); -} +}; module.exports = fetchMock => { describe('Set up and tear down', () => {