diff --git a/README.md b/README.md index 5d6af6e..8a3c48b 100644 --- a/README.md +++ b/README.md @@ -495,8 +495,15 @@ cache.updateInfiniteCache( If you're using [`jest`](https://jestjs.io/) for testing, use `createAPIMockingUtility` to create a shareable utility for mocking network calls. ```typescript +import { setupServer } from 'msw/node'; + +// Set up your server, and start listening. +const server = setupServer(); +server.listen({ onUnhandledRequest: 'error' }); + // Specify your custom "APIEndpoints" type as the generic parameter here. export const useAPIMocking = createAPIMockingUtility({ + server, baseUrl: 'https://my.api.com', }); diff --git a/package.json b/package.json index 1edc6b5..ebbe4d7 100644 --- a/package.json +++ b/package.json @@ -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": "^1.0.0", "prettier": "^2.7.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/hooks.test.tsx b/src/hooks.test.tsx index 07f9a13..b8114f8 100644 --- a/src/hooks.test.tsx +++ b/src/hooks.test.tsx @@ -6,6 +6,7 @@ import { createAPIHooks } from './hooks'; import { createAPIMockingUtility } from './test-utils'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { CacheUtils, EndpointInvalidationMap, RequestPayloadOf } from './types'; +import { setupServer } from 'msw/node'; type TestEndpoints = { 'GET /items': { @@ -54,7 +55,10 @@ const { client, }); +const server = setupServer(); +server.listen({ onUnhandledRequest: 'error' }); const network = createAPIMockingUtility({ + server, baseUrl: 'https://www.lifeomic.com', })(); diff --git a/src/test-utils.test.ts b/src/test-utils.test.ts index cdf749b..26e0699 100644 --- a/src/test-utils.test.ts +++ b/src/test-utils.test.ts @@ -2,6 +2,7 @@ import axios from 'axios'; import { v4 } from 'uuid'; import { createAPIMockingUtility } from './test-utils'; import { APIClient } from './util'; +import { setupServer } from 'msw/node'; describe('createAPIMockingUtility', () => { type TestEndpoints = { @@ -19,7 +20,11 @@ describe('createAPIMockingUtility', () => { }; }; + const server = setupServer(); + server.listen({ onUnhandledRequest: 'error' }); + const useNetworkMocking = createAPIMockingUtility({ + server, baseUrl: 'https://www.lifeomic.com', }); diff --git a/src/test-utils.ts b/src/test-utils.ts index dae0772..5ebd453 100644 --- a/src/test-utils.ts +++ b/src/test-utils.ts @@ -1,7 +1,24 @@ -import * as msw from 'msw'; -import { setupServer, SetupServerApi } from 'msw/node'; +import { RequestHandler, rest } from 'msw'; +/** + * Important: we need to avoid import actual values from these imports: + * - 'msw/node' + * - 'msw/native' + * + * Why: importing from those platform-specific modules can cause import-time + * failures for consumers that are using a different platform. + * + * It's okay to import _types_ from those paths, but not values. + */ +import type { SetupServer } from 'msw/node'; import { PathParamsOf, RoughEndpoints } from './types'; +// This type is the "least common denominator" between the Node +// and browser variants of MSW. +type MSWUsable = { + use: (...handlers: RequestHandler[]) => void; + resetHandlers: () => void; +}; + export type APIMockerResponse = | { status: 200; data: T } | { status: 400 | 401 | 403 | 404 | 500; data: any }; @@ -76,7 +93,7 @@ export type APIMocker = { }; export const createAPIMocker = ( - server: SetupServerApi, + server: MSWUsable, baseUrl: string, ): APIMocker => { const api: APIMocker = {} as any; @@ -94,7 +111,7 @@ export const createAPIMocker = ( | 'post'; server.use( - msw.rest[lowercaseMethod](`${baseUrl}${url}`, async (req, res, ctx) => { + rest[lowercaseMethod](`${baseUrl}${url}`, async (req, res, ctx) => { const resolve = options.once ? res.once : res; if (typeof handlerOrResponse !== 'function') { @@ -164,6 +181,7 @@ export const createAPIMocker = ( }; export type CreateAPIMockingConfig = { + server: SetupServer; baseUrl: string; }; @@ -187,11 +205,11 @@ export type CreateAPIMockingConfig = { * api.mock('GET /something', { status: 200, data: { message: 'test-message' } }) */ export const createAPIMockingUtility = - ({ baseUrl }: CreateAPIMockingConfig) => + ({ + server, + baseUrl, + }: CreateAPIMockingConfig) => () => { - const server = setupServer(); - server.listen({ onUnhandledRequest: 'error' }); - const mocker = createAPIMocker(server, baseUrl); beforeEach(() => { diff --git a/yarn.lock b/yarn.lock index 0fe2be7..ee4f44d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -724,16 +724,16 @@ "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" -"@mswjs/interceptors@^0.17.5": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.17.6.tgz#7f7900f4cd26f70d9f698685e4485b2f4101d26a" - integrity sha512-201pBIWehTURb6q8Gheu4Zhvd3Ox1U4BJq5KiOQsYzkWyfiOG4pwcz5hPZIEryztgrf8/sdwABpvY757xMmfrQ== +"@mswjs/interceptors@^0.17.10": + version "0.17.10" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.17.10.tgz#857b41f30e2b92345ed9a4e2b1d0a08b8b6fcad4" + integrity sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw== dependencies: "@open-draft/until" "^1.0.3" "@types/debug" "^4.1.7" "@xmldom/xmldom" "^0.8.3" debug "^4.3.3" - headers-polyfill "^3.1.0" + headers-polyfill "3.2.5" outvariant "^1.2.1" strict-event-emitter "^0.2.4" web-encoding "^1.1.5" @@ -1916,14 +1916,6 @@ cardinal@^2.1.1: ansicolors "~0.3.2" redeyed "~2.1.0" -chalk@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^2.0.0, chalk@^2.3.2, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3100,7 +3092,7 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== -"graphql@^15.0.0 || ^16.0.0": +graphql@^16.8.1: version "16.8.1" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== @@ -3168,10 +3160,10 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -headers-polyfill@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-3.1.2.tgz#9a4dcb545c5b95d9569592ef7ec0708aab763fbe" - integrity sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA== +headers-polyfill@3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-3.2.5.tgz#6e67d392c9d113d37448fe45014e0afdd168faed" + integrity sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA== hook-std@^2.0.0: version "2.0.0" @@ -3504,10 +3496,10 @@ is-negative-zero@^2.0.2: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== -is-node-process@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.0.1.tgz#4fc7ac3a91e8aac58175fe0578abbc56f2831b23" - integrity sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ== +is-node-process@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" + integrity sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw== is-number-object@^1.0.4: version "1.0.7" @@ -4696,29 +4688,28 @@ ms@^2.0.0, ms@^2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msw@^0.47.4: - version "0.47.4" - resolved "https://registry.yarnpkg.com/msw/-/msw-0.47.4.tgz#5551011609890c6b62a2047055f475a9afae2ad4" - integrity sha512-Psftt8Yfl0+l+qqg9OlmKEsxF8S/vtda0CmlR6y8wTaWrMMzuCDa55n2hEGC0ZRDwuV6FFWc/4CjoDsBpATKBw== +msw@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/msw/-/msw-1.3.2.tgz#35e0271293e893fc3c55116e90aad5d955c66899" + integrity sha512-wKLhFPR+NitYTkQl5047pia0reNGgf0P6a1eTnA5aNlripmiz0sabMvvHcicE8kQ3/gZcI0YiPFWmYfowfm3lA== dependencies: "@mswjs/cookies" "^0.2.2" - "@mswjs/interceptors" "^0.17.5" + "@mswjs/interceptors" "^0.17.10" "@open-draft/until" "^1.0.3" "@types/cookie" "^0.4.1" "@types/js-levenshtein" "^1.1.1" - chalk "4.1.1" + chalk "^4.1.1" chokidar "^3.4.2" cookie "^0.4.2" - graphql "^15.0.0 || ^16.0.0" - headers-polyfill "^3.1.0" + graphql "^16.8.1" + headers-polyfill "3.2.5" inquirer "^8.2.0" - is-node-process "^1.0.1" + is-node-process "^1.2.0" js-levenshtein "^1.1.6" node-fetch "^2.6.7" - outvariant "^1.3.0" + outvariant "^1.4.0" path-to-regexp "^6.2.0" - statuses "^2.0.0" - strict-event-emitter "^0.2.6" + strict-event-emitter "^0.4.3" type-fest "^2.19.0" yargs "^17.3.1" @@ -5110,11 +5101,16 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -outvariant@^1.2.1, outvariant@^1.3.0: +outvariant@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.3.0.tgz#c39723b1d2cba729c930b74bf962317a81b9b1c9" integrity sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ== +outvariant@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.0.tgz#e742e4bda77692da3eca698ef5bfac62d9fba06e" + integrity sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw== + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -5994,11 +5990,6 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -statuses@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - stream-combiner2@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" @@ -6007,13 +5998,18 @@ stream-combiner2@~1.1.1: duplexer2 "~0.1.0" readable-stream "^2.0.2" -strict-event-emitter@^0.2.4, strict-event-emitter@^0.2.6: +strict-event-emitter@^0.2.4: version "0.2.7" resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.7.tgz#5326a21811551995ab5f8158ea250de57fb2b04e" integrity sha512-TavbHJ87WD2tDbKI7bTrmc6U4J4Qjh8E9fVvFkIFw2gCu34Wxstn2Yas0+4D78FJN8DOTEzxiT+udBdraRk4UQ== dependencies: events "^3.3.0" +strict-event-emitter@^0.4.3: + version "0.4.6" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz#ff347c8162b3e931e3ff5f02cfce6772c3b07eb3" + integrity sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"