Skip to content

Commit

Permalink
Merge pull request #553 from wheresrhys/sticky-routes
Browse files Browse the repository at this point in the history
Sticky routes
  • Loading branch information
wheresrhys authored May 11, 2020
2 parents 1d84221 + 155ad22 commit acfc66c
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/_api-lifecycle/resetBehavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: .resetBehavior()
position: 5
versionAdded: 7.0.0
description: |-
Removes all mock routes from the instance of `fetch-mock`, and restores `fetch` to its original implementation if movcking globally. Will not clear data recorded for `fetch`'s calls.
Removes all mock routes from the instance of `fetch-mock`, and restores `fetch` to its original implementation if mocking globally. Will not clear data recorded for `fetch`'s calls. Optionally pass in a `{sticky: true}` option to remove even sticky routes.
content_markdown: |-
`resetBehavior()` is bound to fetchMock, and can be used directly as a callback e.g. `afterEach(fetchMock.resetBehavior)` will work just fine. There is no need for `afterEach(() => fetchMock.resetBehavior())`
---
2 changes: 1 addition & 1 deletion docs/_api-lifecycle/restore_reset.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ navTitle: .restore(), .reset()
position: 3
versionAdded: 7.0.0
description: |-
Resets `fetch()` to its unstubbed state and clears all data recorded for its calls. `restore()` is an alias for `reset()`
Resets `fetch()` to its unstubbed state and clears all data recorded for its calls. `restore()` is an alias for `reset()`. Optionally pass in a `{sticky: true}` option to remove even sticky routes.
content_markdown: |-
Both methods are bound to fetchMock, and can be used directly as callbacks e.g. `afterEach(fetchMock.reset)` will work just fine. There is no need for `afterEach(() => fetchMock.reset())`
---
6 changes: 6 additions & 0 deletions docs/_api-mocking/mock_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ parameters:
- Integer
content: |-
Delays responding for the number of milliseconds specified.
- name: sticky
versionAdded: 9.7.0
types:
- Boolean
content: |-
Avoids a route being removed when `reset()`, `restore()` or `resetBehavior()` are called. *Note - this does not preserve the history of calls to the route*
- name: sendAsJson
versionAdded: 4.1.0
default: true
Expand Down
13 changes: 13 additions & 0 deletions docs/_api-mocking/mock_sticky.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: '.sticky()'
position: 3
versionAdded: 9.7.0
description: |-
Shorthand for `mock()` which creates a route that persists even when `restore()`, `reset()` or `resetbehavior()` are called;
parentMethodGroup: mocking
content_markdown: |-
This method is particularly useful for setting up fixtures that must remain in place for all tests, e.g.
```js
fetchMock.sticky(/config-hub.com/, require('./fixtures/start-up-config.json'))
```
---
33 changes: 27 additions & 6 deletions src/lib/set-up-and-tear-down.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const defineGreedyShorthand = (methodName, underlyingMethod) => {
};
};

defineShorthand('sticky', 'mock', { sticky: true });
defineShorthand('once', 'mock', { repeat: 1 });
defineGreedyShorthand('any', 'mock');
defineGreedyShorthand('anyOnce', 'once');
Expand All @@ -100,14 +101,34 @@ defineGreedyShorthand('anyOnce', 'once');
defineGreedyShorthand(`${method}AnyOnce`, `${method}Once`);
});

FetchMock.resetBehavior = function () {
if (this.realFetch) {
const mochaAsyncHookWorkaround = (options) => {
// HACK workaround for this https://github.com/mochajs/mocha/issues/4280
// Note that it doesn't matter that we call it _before_ carrying out all
// the things resetBehavior does as everything in there is synchronous
if (typeof options === 'function') {
console.warn(`Deprecated: Passing fetch-mock reset methods
directly in as handlers for before/after test runner hooks.
Wrap in an arrow function instead e.g. \`() => fetchMock.restore()\``);
options();
}
};

const getRouteRemover = ({ sticky: removeStickyRoutes }) => (routes) =>
removeStickyRoutes ? [] : routes.filter(({ sticky }) => sticky);

FetchMock.resetBehavior = function (options = {}) {
mochaAsyncHookWorkaround(options);
const removeRoutes = getRouteRemover(options);

this.routes = removeRoutes(this.routes);
this._uncompiledRoutes = removeRoutes(this._uncompiledRoutes);

if (this.realFetch && !this.routes.length) {
this.global.fetch = this.realFetch;
this.realFetch = undefined;
}

this.fallbackResponse = undefined;
this.routes = [];
this._uncompiledRoutes = [];
return this;
};

Expand All @@ -118,8 +139,8 @@ FetchMock.resetHistory = function () {
return this;
};

FetchMock.restore = FetchMock.reset = function () {
this.resetBehavior();
FetchMock.restore = FetchMock.reset = function (options) {
this.resetBehavior(options);
this.resetHistory();
return this;
};
Expand Down
1 change: 1 addition & 0 deletions test/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ module.exports = (fetchMock, theGlobal, fetch, AbortController) => {
require('./specs/shorthands.test')(fetchMock);
require('./specs/spy.test')(fetchMock, theGlobal, fetch);
require('./specs/user-defined-matchers.test')(fetchMock);
require('./specs/sticky-routes.test')(fetchMock, theGlobal);
});
};
14 changes: 13 additions & 1 deletion test/specs/shorthands.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,23 @@ module.exports = (fetchMock) => {
});
afterEach(() => {
fm.compileRoute.resetHistory();
fm.restore();
fm.restore({ sticky: true });
});

after(() => fm.compileRoute.restore());

it('has sticky() shorthand method', () => {
fm.sticky('a', 'b');
fm.sticky('c', 'd', { opt: 'e' });
expectRoute('a', 'b', {
sticky: true,
});
expectRoute('c', 'd', {
opt: 'e',
sticky: true,
});
});

it('has once() shorthand method', () => {
fm.once('a', 'b');
fm.once('c', 'd', { opt: 'e' });
Expand Down
138 changes: 138 additions & 0 deletions test/specs/sticky-routes.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
const chai = require('chai');
chai.use(require('sinon-chai'));
const expect = chai.expect;
const sinon = require('sinon');

module.exports = (fetchMock, theGlobal) => {
describe('sticky routes', () => {
describe('effect on routes', () => {
let fm;
before(() => {
fm = fetchMock.createInstance();
fm.config.warnOnUnmatched = false;
});

afterEach(() => fm.restore({ sticky: true }));

describe('resetting behaviour', () => {
it('behaviour resists resetBehavior calls', async () => {
fm.mock('*', 200, { sticky: true }).resetBehavior();
expect(fm.routes.length).to.equal(1);
});

it('behaviour resists restore calls', async () => {
fm.mock('*', 200, { sticky: true }).restore();
expect(fm.routes.length).to.equal(1);
});

it('behaviour resists reset calls', async () => {
fm.mock('*', 200, { sticky: true }).reset();
expect(fm.routes.length).to.equal(1);
});

it('behaviour does not resist resetBehavior calls when sent `sticky: true`', async () => {
fm.mock('*', 200, { sticky: true }).resetBehavior({ sticky: true });
expect(fm.routes.length).to.equal(0);
});

it('behaviour does not resist restore calls when sent `sticky: true`', async () => {
fm.mock('*', 200, { sticky: true }).restore({ sticky: true });
expect(fm.routes.length).to.equal(0);
});

it('behaviour does not resist reset calls when sent `sticky: true`', async () => {
fm.mock('*', 200, { sticky: true }).reset({ sticky: true });
expect(fm.routes.length).to.equal(0);
});
});

describe('resetting history', () => {
it('history does not resist resetHistory calls', async () => {
fm.mock('*', 200, { sticky: true });
fm.fetchHandler('http://la.com');
fm.resetHistory();
expect(fm.called()).to.be.false;
});

it('history does not resist restore calls', async () => {
fm.mock('*', 200, { sticky: true });
fm.fetchHandler('http://la.com');
fm.restore();
expect(fm.called()).to.be.false;
});

it('history does not resist reset calls', async () => {
fm.mock('*', 200, { sticky: true });
fm.fetchHandler('http://la.com');
fm.reset();
expect(fm.called()).to.be.false;
});
});

describe('multiple routes', () => {
it('can have multiple sticky routes', async () => {
fm.mock('*', 200, { sticky: true })
.mock('http://la.com', 200, { sticky: true })
.resetBehavior();
expect(fm.routes.length).to.equal(2);
});

it('can have a sticky route before non-sticky routes', async () => {
fm.mock('*', 200, { sticky: true })
.mock('http://la.com', 200)
.resetBehavior();
expect(fm.routes.length).to.equal(1);
expect(fm.routes[0].url).to.equal('*');
});

it('can have a sticky route after non-sticky routes', async () => {
fm.mock('*', 200)
.mock('http://la.com', 200, { sticky: true })
.resetBehavior();
expect(fm.routes.length).to.equal(1);
expect(fm.routes[0].url).to.equal('http://la.com');
});
});
});
describe('global mocking', () => {
let originalFetch;
before(() => {
originalFetch = theGlobal.fetch = sinon
.stub()
.returns(Promise.resolve());
});
afterEach(() => fetchMock.restore({ sticky: true }));

it('global mocking resists resetBehavior calls', async () => {
fetchMock.mock(/a/, 200, { sticky: true }).resetBehavior();
expect(theGlobal.fetch).not.to.equal(originalFetch);
});

it('global mocking does not resist resetBehavior calls when sent `sticky: true`', async () => {
fetchMock
.mock(/a/, 200, { sticky: true })
.resetBehavior({ sticky: true });
expect(theGlobal.fetch).to.equal(originalFetch);
});
});

describe('sandboxes', () => {
it('sandboxed instances should inherit stickiness', async () => {
const sbx1 = fetchMock
.sandbox()
.mock(/a/, 200, { sticky: true })
.catch(300);

const sbx2 = sbx1.sandbox().resetBehavior();

expect(sbx1.routes.length).to.equal(1);
expect(sbx2.routes.length).to.equal(1);

sbx2.resetBehavior({ sticky: true });

expect(sbx1.routes.length).to.equal(1);
expect(sbx2.routes.length).to.equal(0);
});
});
});
};

0 comments on commit acfc66c

Please sign in to comment.