Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detached route creation #587

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/Route/index.js
Expand Up @@ -10,11 +10,10 @@ const isUrlMatcher = (matcher) =>
const isFunctionMatcher = (matcher) => typeof matcher === 'function';

class Route {
constructor(args, fetchMock) {
this.fetchMock = fetchMock;
constructor(...args) {
const debug = getDebug('compileRoute()');
debug('Compiling route');
this.init(args);
this.init(...args);
this.sanitize();
this.validate();
this.generateMatcher();
Expand All @@ -34,9 +33,7 @@ class Route {
}
}

init(args) {
const [matcher, response, options = {}] = args;

init(matcher, response, options = {}) {
const routeConfig = {};

if (isUrlMatcher(matcher) || isFunctionMatcher(matcher)) {
Expand Down Expand Up @@ -84,16 +81,18 @@ class Route {
const activeMatchers = Route.registeredMatchers
.map(
({ name, matcher, usesBody }) =>
this[name] && { matcher: matcher(this, this.fetchMock), usesBody }
this[name] && { matcher: matcher(this), usesBody }
)
.filter((matcher) => Boolean(matcher));

this.usesBody = activeMatchers.some(({ usesBody }) => usesBody);

debug('Compiled matcher for route');
setDebugNamespace();
this.matcher = (url, options = {}, request) =>
activeMatchers.every(({ matcher }) => matcher(url, options, request));
this.matcher = (url, options = {}, request, fetchMock) =>
activeMatchers.every(({ matcher }) =>
matcher(url, options, request, fetchMock)
);
}

limit() {
Expand Down Expand Up @@ -141,10 +140,14 @@ class Route {
static addMatcher(matcher) {
Route.registeredMatchers.push(matcher);
}

static compileRoute(...config) {
return new Route(...config);
}
}

Route.registeredMatchers = [];

builtInMatchers.forEach(Route.addMatcher);

module.exports = Route;
module.exports = { Route };
6 changes: 3 additions & 3 deletions src/Route/matchers.js
Expand Up @@ -129,12 +129,12 @@ const getParamsMatcher = ({ params: expectedParams, url: matcherUrl }) => {
};
};

const getBodyMatcher = (route, fetchMock) => {
const matchPartialBody = fetchMock.getOption('matchPartialBody', route);
const getBodyMatcher = (route) => {
const { body: expectedBody } = route;

debug('Generating body matcher');
return (url, { body, method = 'get' }) => {
return (url, { body, method = 'get' }, request, fetchMock) => {
const matchPartialBody = fetchMock.getOption('matchPartialBody', route);
debug('Attempting to match body');
if (method.toLowerCase() === 'get') {
debug(' GET request - skip matching body');
Expand Down
2 changes: 1 addition & 1 deletion src/lib/fetch-handler.js
Expand Up @@ -281,7 +281,7 @@ FetchMock.generateResponse = async function ({
FetchMock.router = function (url, options, request) {
const route = this.routes.find((route, i) => {
debug(`Trying to match route ${i}`);
return route.matcher(url, options, request);
return route.matcher(url, options, request, this);
});

if (route) {
Expand Down
13 changes: 2 additions & 11 deletions src/lib/index.js
Expand Up @@ -2,14 +2,9 @@ const { debug } = require('./debug');
const setUpAndTearDown = require('./set-up-and-tear-down');
const fetchHandler = require('./fetch-handler');
const inspecting = require('./inspecting');
const Route = require('../Route');

const { Route } = require('../Route');
const FetchMock = Object.assign({}, fetchHandler, setUpAndTearDown, inspecting);

FetchMock.addMatcher = function (matcher) {
Route.addMatcher(matcher);
};

FetchMock.config = {
fallbackToNetwork: false,
includeContentLength: true,
Expand All @@ -23,7 +18,7 @@ FetchMock.createInstance = function () {
const instance = Object.create(FetchMock);
instance._uncompiledRoutes = (this._uncompiledRoutes || []).slice();
instance.routes = instance._uncompiledRoutes.map((config) =>
this.compileRoute(config)
Route.compileRoute(...config)
);
instance.fallbackResponse = this.fallbackResponse || undefined;
instance.config = Object.assign({}, this.config || FetchMock.config);
Expand All @@ -33,10 +28,6 @@ FetchMock.createInstance = function () {
return instance;
};

FetchMock.compileRoute = function (config) {
return new Route(config, this);
};

FetchMock.bindMethods = function () {
this.fetchHandler = FetchMock.fetchHandler.bind(this);
this.reset = this.restore = FetchMock.reset.bind(this);
Expand Down
9 changes: 4 additions & 5 deletions src/lib/inspecting.js
@@ -1,17 +1,16 @@
const { setDebugPhase, setDebugNamespace, debug } = require('./debug');
const { normalizeUrl } = require('./request-utils');
const Route = require('../Route');
const { Route } = require('../Route');
const FetchMock = {};
const isName = (nameOrMatcher) =>
typeof nameOrMatcher === 'string' && /^[\da-zA-Z\-]+$/.test(nameOrMatcher);

const filterCallsWithMatcher = function (matcher, options = {}, calls) {
({ matcher } = new Route(
[Object.assign({ matcher, response: 'ok' }, options)],
this
({ matcher } = Route.compileRoute(
Object.assign({ matcher, response: 'ok' }, options)
));
return calls.filter(({ url, options }) =>
matcher(normalizeUrl(url), options)
matcher(normalizeUrl(url), options, null, this)
);
};

Expand Down
49 changes: 35 additions & 14 deletions src/lib/set-up-and-tear-down.js
@@ -1,18 +1,28 @@
const { debug, setDebugPhase } = require('./debug');
const FetchMock = {};

FetchMock.mock = function (...args) {
const { Route } = require('../Route');

FetchMock.addMatcher = function (matcher) {
Route.addMatcher(matcher);
};

FetchMock.$mock = function (...args) {
setDebugPhase('setup');
this._mock();
if (args.length) {
this.addRoute(args);
return this.addRoute(args);
}
};

return this._mock();
FetchMock.mock = function (...args) {
this.$mock(...args);
return this;
};

FetchMock.addRoute = function (uncompiledRoute) {
debug('Adding route', uncompiledRoute);
const route = this.compileRoute(uncompiledRoute);
const route = Route.compileRoute(...uncompiledRoute);
const clashes = this.routes.filter(({ identifier, method }) => {
const isMatch =
typeof identifier === 'function'
Expand Down Expand Up @@ -43,6 +53,7 @@ FetchMock.addRoute = function (uncompiledRoute) {

this._uncompiledRoutes.push(uncompiledRoute);
this.routes.push(route);
return route;
};

FetchMock._mock = function () {
Expand Down Expand Up @@ -75,7 +86,7 @@ FetchMock.spy = function (route) {
: this.catch(this.getNativeFetch());
};

const defineShorthand = (methodName, underlyingMethod, shorthandOptions) => {
const _defineShorthand = (methodName, underlyingMethod, shorthandOptions) => {
FetchMock[methodName] = function (matcher, response, options) {
return this[underlyingMethod](
matcher,
Expand All @@ -85,22 +96,32 @@ const defineShorthand = (methodName, underlyingMethod, shorthandOptions) => {
};
};

const defineGreedyShorthand = (methodName, underlyingMethod) => {
const defineShorthands = (methodName, underlyingMethod, shorthandOptions) => {
_defineShorthand(methodName, underlyingMethod, shorthandOptions);
_defineShorthand(`$${methodName}`, `$${underlyingMethod}`, shorthandOptions);
};

const _defineGreedyShorthand = (methodName, underlyingMethod) => {
FetchMock[methodName] = function (response, options) {
return this[underlyingMethod]({}, response, options);
};
};

defineShorthand('sticky', 'mock', { sticky: true });
defineShorthand('once', 'mock', { repeat: 1 });
defineGreedyShorthand('any', 'mock');
defineGreedyShorthand('anyOnce', 'once');
const defineGreedyShorthands = (methodName, underlyingMethod) => {
_defineGreedyShorthand(methodName, underlyingMethod);
_defineGreedyShorthand(`$${methodName}`, `$${underlyingMethod}`);
};

defineShorthands('sticky', 'mock', { sticky: true });
defineShorthands('once', 'mock', { repeat: 1 });
defineGreedyShorthands('any', 'mock');
defineGreedyShorthands('anyOnce', 'once');

['get', 'post', 'put', 'delete', 'head', 'patch'].forEach((method) => {
defineShorthand(method, 'mock', { method });
defineShorthand(`${method}Once`, 'once', { method });
defineGreedyShorthand(`${method}Any`, method);
defineGreedyShorthand(`${method}AnyOnce`, `${method}Once`);
defineShorthands(method, 'mock', { method });
defineShorthands(`${method}Once`, 'once', { method });
defineGreedyShorthands(`${method}Any`, method);
defineGreedyShorthands(`${method}AnyOnce`, `${method}Once`);
});

const mochaAsyncHookWorkaround = (options) => {
Expand Down
23 changes: 10 additions & 13 deletions test/specs/set-up-and-tear-down.test.js
Expand Up @@ -4,6 +4,7 @@ const expect = chai.expect;
const sinon = require('sinon');

const { fetchMock } = testGlobals;
const { Route } = require('../../src/Route');
describe('Set up and tear down', () => {
let fm;
before(() => {
Expand Down Expand Up @@ -42,12 +43,12 @@ describe('Set up and tear down', () => {

describe('parameters', () => {
beforeEach(() => {
sinon.spy(fm, 'compileRoute');
sinon.spy(Route, 'compileRoute');
sinon.stub(fm, '_mock').returns(fm);
});

afterEach(() => {
fm.compileRoute.restore();
Route.compileRoute.restore();
fm._mock.restore();
});

Expand All @@ -57,13 +58,13 @@ describe('Set up and tear down', () => {
response: 200,
};
expect(() => fm.mock(config)).not.to.throw();
expect(fm.compileRoute).calledWith([config]);
expect(Route.compileRoute).calledWith(config);
expect(fm._mock).called;
});

it('accepts matcher, route pairs', () => {
expect(() => fm.mock('*', 200)).not.to.throw();
expect(fm.compileRoute).calledWith(['*', 200]);
expect(Route.compileRoute).calledWith('*', 200);
expect(fm._mock).called;
});

Expand All @@ -74,14 +75,10 @@ describe('Set up and tear down', () => {
some: 'prop',
})
).not.to.throw();
expect(fm.compileRoute).calledWith([
'*',
'ok',
{
method: 'PUT',
some: 'prop',
},
]);
expect(Route.compileRoute).calledWith('*', 'ok', {
method: 'PUT',
some: 'prop',
});
expect(fm._mock).called;
});

Expand All @@ -95,7 +92,7 @@ describe('Set up and tear down', () => {

it('can be called with no parameters', () => {
expect(() => fm.mock()).not.to.throw();
expect(fm.compileRoute).not.called;
expect(Route.compileRoute).not.called;
expect(fm._mock).called;
});

Expand Down
9 changes: 5 additions & 4 deletions test/specs/shorthands.test.js
Expand Up @@ -4,6 +4,7 @@ const expect = chai.expect;
const sinon = require('sinon');

const { fetchMock } = testGlobals;
const { Route } = require('../../src/Route');
describe('shorthands', () => {
let fm;
let expectRoute;
Expand All @@ -25,16 +26,16 @@ describe('shorthands', () => {

before(() => {
fm = fetchMock.createInstance();
sinon.spy(fm, 'compileRoute');
sinon.spy(Route, 'compileRoute');
fm.config.warnOnUnmatched = false;
expectRoute = (...args) => expect(fm.compileRoute).calledWith(args);
expectRoute = (...args) => expect(Route.compileRoute).calledWith(...args);
});
afterEach(() => {
fm.compileRoute.resetHistory();
Route.compileRoute.resetHistory();
fm.restore({ sticky: true });
});

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

it('has sticky() shorthand method', () => {
fm.sticky('a', 'b');
Expand Down