Skip to content

Commit

Permalink
feat: can now access express parameters in responses
Browse files Browse the repository at this point in the history
  • Loading branch information
wheresrhys committed Jul 21, 2024
1 parent 9745435 commit 41e2475
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 22 deletions.
2 changes: 2 additions & 0 deletions docs/docs/@fetch-mock/core/CallHistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ sidebar_position: 4

Calls are recorded, and returned, in a standard format with the following properties:

- `[string|Request,Object]` - the original arguments passed in to `fetch`
- `{string} url` - The url being fetched
- `{NormalizedRequestOptions} options` - The options passed in to the fetch (may be derived from a `Request` if one was used)
- `{Request} [request]` - The `Request` passed to fetch, if one was used
- `{Route} [route]` - The route used to handle the request
- `{Response} [response]` - The `Response` returned to the user
- `{Object.<string,string>}` - Any express parameters extracted from the `url`
- `{Promise<any>[]} pendingPromises` - An internal structure used by the `.flush()` method documented below

## Filtering
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/@fetch-mock/core/route/matcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ When the `express:` keyword is used in a string matcher, it can be combined with
}
```

The values of express parameters are made available in the `expressParams` property when
- [Inspecting call history](/fetch-mock/docs/@fetch-mock/core/CallHistory#calllog-schema)
- [Using a function to construct a response](/fetch-mock/docs/@fetch-mock/core/route/response#function)

## Other matching criteria

### method
Expand Down
49 changes: 27 additions & 22 deletions packages/core/src/Matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,21 @@ const stringMatchers = {
},
express: (targetString) => {
const urlRX = regexparam.parse(targetString);
return ({ url }) => urlRX.pattern.test(getPath(url));
return (callLog) => {
const vals = urlRX.pattern.exec(getPath(callLog.url));
if (!vals) {
callLog.expressParams = {};
return false;
}
vals.shift();
/** @type {Object.<string,string>} */
callLog.expressParams = urlRX.keys.reduce(
(map, paramName, i) =>
vals[i] ? Object.assign(map, { [paramName]: vals[i] }) : map,
{},
);
return true;
};
},
path:
(targetString) =>
Expand Down Expand Up @@ -124,30 +138,21 @@ const getQueryStringMatcher = ({ query: passedQuery }) => {
/**
* @type {MatcherGenerator}
*/
const getParamsMatcher = ({ params: expectedParams, url: matcherUrl }) => {
const getParamsMatcher = ({ params: expectedParams, url }) => {
if (!expectedParams) {
return;
}
if (typeof matcherUrl === 'string') {
if (!/express:/.test(matcherUrl)) {
throw new Error(
'fetch-mock: matching on params is only possible when using an express: matcher',
);
}
const expectedKeys = Object.keys(expectedParams);
const re = regexparam.parse(matcherUrl.replace(/^express:/, ''));
return ({ url }) => {
const vals = re.pattern.exec(getPath(url)) || [];
vals.shift();
/** @type {Object.<string,string>} */
const params = re.keys.reduce(
(map, paramName, i) =>
vals[i] ? Object.assign(map, { [paramName]: vals[i] }) : map,
{},
);
return expectedKeys.every((key) => params[key] === expectedParams[key]);
};
if (!(typeof url === 'string' && /express:/.test(url))) {
throw new Error(
'fetch-mock: matching on params is only possible when using an express: matcher',
);
}
const expectedKeys = Object.keys(expectedParams);
return ({ expressParams = {} }) => {
return expectedKeys.every(
(key) => expressParams[key] === expectedParams[key],
);
};
};
/**
* @type {MatcherGenerator}
Expand Down Expand Up @@ -238,11 +243,11 @@ const getUrlMatcher = (route) => {

/** @type {MatcherDefinition[]} */
export const builtInMatchers = [
{ name: 'url', matcher: getUrlMatcher },
{ name: 'query', matcher: getQueryStringMatcher },
{ name: 'method', matcher: getMethodMatcher },
{ name: 'headers', matcher: getHeaderMatcher },
{ name: 'params', matcher: getParamsMatcher },
{ name: 'body', matcher: getBodyMatcher, usesBody: true },
{ name: 'matcherFunction', matcher: getFunctionMatcher },
{ name: 'url', matcher: getUrlMatcher },
];
33 changes: 33 additions & 0 deletions packages/core/src/__tests__/Matchers/express.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,37 @@ describe('express path parameter matching', () => {
true,
);
});

it('can match based on the existence, not value, of a parameter', () => {
const route = new Route({
url: 'express:/type/:instance',
response: 200,
});
expect(route.matcher({ url: '/nottype/a' })).toBe(false);
expect(route.matcher({ url: '/type/a' })).toBe(true);
});
it('writes parameter values to the callLog', () => {
const route = new Route({
url: 'express:/type/:instance',
response: 200,
params: { instance: 'b' },
});
const callLog = { url: '/type/a' };
route.matcher(callLog);
expect(callLog.expressParams).toEqual({ instance: 'a' });

const callLog2 = { url: '/type/b' };
route.matcher(callLog2);
expect(callLog2.expressParams).toEqual({ instance: 'b' });
});

it('writes parameter values to the callLog even if not matched on', () => {
const route = new Route({
url: 'express:/type/:instance',
response: 200,
});
const callLog = { url: '/type/a' };
route.matcher(callLog);
expect(callLog.expressParams).toEqual({ instance: 'a' });
});
});

0 comments on commit 41e2475

Please sign in to comment.