Skip to content

Commit

Permalink
feat: support msw v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Swain Molster committed Oct 26, 2023
1 parent 649f338 commit 810d5b6
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 505 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.2.1",
"jest-environment-jsdom": "^29.2.1",
"msw": "^0.47.4",
"msw": "^2.0.0",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
3 changes: 3 additions & 0 deletions src/test-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* @jest-environment node
*/
import axios from 'axios';
import { v4 } from 'uuid';
import { createAPIMockingUtility } from './test-utils';
Expand Down
102 changes: 56 additions & 46 deletions src/test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as msw from 'msw';
import { setupServer, SetupServerApi } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { setupServer, SetupServer } from 'msw/node';
import { PathParamsOf, RoughEndpoints } from './types';

export type APIMockerResponse<T> =
Expand Down Expand Up @@ -32,6 +32,14 @@ type MockFunction<Endpoints extends RoughEndpoints> = <
| MockRequestHandler<Endpoints, Route>,
) => APIMocker<Endpoints>;

const gatherHeaders = (headers: Headers) => {
const headersObj: Record<string, string> = {};
headers.forEach((value, key) => {
headersObj[key] = value;
});
return headersObj;
};

export type APIMocker<Endpoints extends RoughEndpoints> = {
/**
* Persistently mocks the specified `route` using the provided handler or
Expand Down Expand Up @@ -76,7 +84,7 @@ export type APIMocker<Endpoints extends RoughEndpoints> = {
};

export const createAPIMocker = <Endpoints extends RoughEndpoints>(
server: SetupServerApi,
server: SetupServer,
baseUrl: string,
): APIMocker<Endpoints> => {
const api: APIMocker<Endpoints> = {} as any;
Expand All @@ -94,51 +102,53 @@ export const createAPIMocker = <Endpoints extends RoughEndpoints>(
| 'post';

server.use(
msw.rest[lowercaseMethod](`${baseUrl}${url}`, async (req, res, ctx) => {
const resolve = options.once ? res.once : res;

if (typeof handlerOrResponse !== 'function') {
return resolve(
ctx.status(handlerOrResponse.status),
ctx.json(handlerOrResponse.data),
);
}

const mockRequest = {
headers: req.headers.all(),
params: req.params as PathParamsOf<typeof route>,
};

let mockedResponse: APIMockerResponse<
Endpoints[typeof route]['Response']
>;

if (['get', 'delete'].includes(lowercaseMethod)) {
const query: Record<string, string> = {};
for (const [key, value] of req.url.searchParams.entries()) {
query[key] = value;
http[lowercaseMethod](
`${baseUrl}${url}`,
async ({ params, request }) => {
if (typeof handlerOrResponse !== 'function') {
return HttpResponse.json(handlerOrResponse.data, {
status: handlerOrResponse.status,
});
}
// @ts-expect-error TypeScript isn't smart enough to narrow down
// the GET/DELETE case here.
mockedResponse = await handlerOrResponse({
...mockRequest,
query,
});
} else {
const body = await req.json();
// @ts-expect-error TypeScript isn't smart enough to narrow down
// the GET/DELETE case here.
mockedResponse = await handlerOrResponse({
...mockRequest,
body,
});
}

return resolve(
ctx.status(mockedResponse.status),
ctx.json(mockedResponse.data),
);
}),
const mockRequest = {
headers: gatherHeaders(request.headers),
params: params as PathParamsOf<typeof route>,
};

let mockedResponse: APIMockerResponse<
Endpoints[typeof route]['Response']
>;

if (['get', 'delete'].includes(lowercaseMethod)) {
const query: Record<string, string> = {};
for (const [key, value] of new URL(
request.url,
).searchParams.entries()) {
query[key] = value;
}
// @ts-expect-error TypeScript isn't smart enough to narrow down
// the GET/DELETE case here.
mockedResponse = await handlerOrResponse({
...mockRequest,
query,
});
} else {
const body = await request.json();
// @ts-expect-error TypeScript isn't smart enough to narrow down
// the GET/DELETE case here.
mockedResponse = await handlerOrResponse({
...mockRequest,
body,
});
}

return HttpResponse.json(mockedResponse.data, {
status: mockedResponse.status,
});
},
{ once: options.once },
),
);

return api;
Expand Down
Loading

0 comments on commit 810d5b6

Please sign in to comment.