├── .circleci └── config.yml ├── .codeclimate.yml ├── .github └── workflows │ ├── deploy.yml │ ├── release-please.yml │ └── test-deploy.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── .release-please-manifest.json ├── ISSUE_TEMPLATE ├── LICENSE ├── README.md ├── commitlint.config.js ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── blog │ ├── 2024-07-20-new-beginnings.md │ ├── 2024-07-21-introducing-core.md │ └── 2024-09-24-fetch-mock-12.md ├── docs │ ├── API │ │ ├── CallHistory.md │ │ ├── _category_.json │ │ ├── mocking-and-spying.md │ │ ├── more-routing-methods.md │ │ ├── resetting.md │ │ └── route │ │ │ ├── _category_.json │ │ │ ├── index.md │ │ │ ├── matcher.md │ │ │ ├── options.md │ │ │ └── response.md │ ├── Usage │ │ ├── _category_.json │ │ ├── configuration.md │ │ ├── installation.md │ │ ├── quickstart.md │ │ ├── requirements.md │ │ └── upgrade-guide.md │ ├── index.md │ ├── legacy-api │ │ ├── API │ │ │ ├── Inspection │ │ │ │ ├── _category_.json │ │ │ │ ├── done.md │ │ │ │ ├── flush.md │ │ │ │ └── inspecting-calls.md │ │ │ ├── Lifecycle │ │ │ │ ├── _category_.json │ │ │ │ ├── resetting.md │ │ │ │ └── sandbox.md │ │ │ ├── Mocking │ │ │ │ ├── Parameters │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── matcher.md │ │ │ │ │ ├── options.md │ │ │ │ │ └── response.md │ │ │ │ ├── _category_.json │ │ │ │ ├── add-matcher.md │ │ │ │ ├── catch.md │ │ │ │ ├── mock.md │ │ │ │ ├── shorthands.md │ │ │ │ └── spy.md │ │ │ └── _category_.json │ │ ├── Troubleshooting │ │ │ ├── _category_.json │ │ │ ├── cookies.md │ │ │ ├── custom-classes.md │ │ │ ├── debug-mode.md │ │ │ ├── global-non-global.md │ │ │ ├── importing.md │ │ │ └── troubleshooting.md │ │ ├── Usage │ │ │ ├── Usage.md │ │ │ ├── _category_.json │ │ │ ├── cheatsheet.md │ │ │ ├── configuration.md │ │ │ ├── installation.md │ │ │ ├── quickstart.md │ │ │ ├── requirements.md │ │ │ └── versions.md │ │ └── index.md │ └── wrappers │ │ ├── index.md │ │ ├── jest.md │ │ └── vitest.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ └── index.module.css └── static │ ├── .nojekyll │ └── img │ ├── docusaurus-social-card.jpg │ ├── docusaurus.png │ ├── favicon.ico │ ├── logo.svg │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ └── undraw_docusaurus_tree.svg ├── eslint.config.js ├── import-compat ├── package.json ├── ts-cjs.ts ├── ts-esm.mts └── ts-esm.ts ├── jest.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── packages ├── codemods │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── fixtures │ │ │ │ ├── extra-vars.js │ │ │ │ ├── jsx.jsx │ │ │ │ ├── tsx.tsx │ │ │ │ └── typescript.ts │ │ │ ├── identifying-fetchmock-instances.test.js │ │ │ ├── integration.test.js │ │ │ ├── method-codemods.test.js │ │ │ ├── option-codemods.test.js │ │ │ └── package.json │ │ ├── codemods │ │ │ ├── methods.js │ │ │ └── options.js │ │ └── index.js │ └── try.js ├── fetch-mock │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── CallHistory.ts │ │ ├── FetchMock.ts │ │ ├── IsSubsetOf.ts │ │ ├── Matchers.ts │ │ ├── RequestUtils.ts │ │ ├── Route.ts │ │ ├── Router.ts │ │ ├── StatusTextMap.ts │ │ ├── TypeDescriptor.ts │ │ ├── __tests__ │ │ │ ├── CallHistory.test.js │ │ │ ├── FetchMock │ │ │ │ ├── flush.test.js │ │ │ │ ├── instance-management.test.js │ │ │ │ ├── mock-and-spy.test.js │ │ │ │ ├── response-construction.test.js │ │ │ │ ├── response-negotiation.test.js │ │ │ │ └── routing.test.js │ │ │ ├── Matchers │ │ │ │ ├── body.test.js │ │ │ │ ├── express.test.js │ │ │ │ ├── function.test.js │ │ │ │ ├── headers.test.js │ │ │ │ ├── method.test.js │ │ │ │ ├── query-string.test.js │ │ │ │ ├── route-config-object.test.js │ │ │ │ └── url.test.js │ │ │ ├── router-integration.test.js │ │ │ └── spec-compliance.test.js │ │ └── index.ts │ ├── tsconfig.cjs.json │ └── tsconfig.esm.json ├── jest │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── custom-jsdom-environment.ts │ │ │ ├── extensions.spec.ts │ │ │ ├── jsdom.spec.ts │ │ │ └── reset-methods.spec.js │ │ ├── index.ts │ │ ├── jest-extensions.ts │ │ └── types.ts │ ├── tsconfig.cjs.json │ └── tsconfig.esm.json └── vitest │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ ├── __tests__ │ │ ├── extensions.spec.ts │ │ └── reset-methods.spec.js │ ├── index.ts │ ├── types.ts │ └── vitest-extensions.ts │ ├── tsconfig.cjs.json │ └── tsconfig.esm.json ├── release-please-config.json ├── scripts ├── circleci-npm-publish.sh └── declare-dist-type.js ├── tea.yaml └── tsconfig.base.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | 3 | references: 4 | triggerable-by-tag: &triggerable-by-tag 5 | filters: 6 | tags: 7 | only: /.*/ 8 | 9 | node18: &node18 10 | docker: 11 | - image: cimg/node:18.11 12 | nodelts: &nodelts 13 | docker: 14 | - image: cimg/node:lts 15 | browsers: &browsers 16 | docker: 17 | - image: cimg/node:lts-browsers 18 | nodecurrent: &nodecurrent 19 | docker: 20 | - image: cimg/node:current 21 | 22 | workspace: &workspace 23 | attach_workspace: 24 | at: ~/project 25 | persist: &persist 26 | persist_to_workspace: 27 | root: . 28 | paths: 29 | - . 30 | after-build-test-lint: &after-build-test-lint 31 | requires: 32 | - test 33 | - lint 34 | - build 35 | after-build-lint: &after-build-lint 36 | requires: 37 | - lint 38 | - build 39 | 40 | jobs: 41 | checkout_code: 42 | <<: *nodelts 43 | steps: 44 | - checkout 45 | - restore_cache: 46 | key: npm-cache-{{ checksum "package-lock.json" }} 47 | - run: if [ ! -d "node_modules" ]; then npm install --no-package-lock; fi 48 | - save_cache: 49 | key: npm-cache-{{ checksum "package-lock.json" }} 50 | paths: 51 | - node_modules 52 | - *persist 53 | 54 | build: 55 | <<: *nodelts 56 | steps: 57 | - *workspace 58 | - run: npm run build 59 | - *persist 60 | lint: 61 | <<: *nodelts 62 | steps: 63 | - *workspace 64 | - run: npm run lint:ci 65 | - run: npm run prettier:ci 66 | 67 | typecheck: 68 | <<: *nodelts 69 | steps: 70 | - *workspace 71 | - run: npm run types:check --noEmit=true 72 | test: 73 | <<: *nodelts 74 | steps: 75 | - *workspace 76 | - run: npm run test:ci 77 | - store_test_results: 78 | path: test-results 79 | test-node18: 80 | <<: *node18 81 | steps: 82 | - *workspace 83 | - run: npm run test:ci 84 | test-browser: 85 | <<: *browsers 86 | steps: 87 | - *workspace 88 | - run: npx playwright install 89 | - run: npm run test:browser 90 | test-jest: 91 | <<: *nodelts 92 | steps: 93 | - *workspace 94 | - run: npm run test:jest 95 | - store_test_results: 96 | path: test-results 97 | module-compat: 98 | <<: *nodelts 99 | steps: 100 | - *workspace 101 | - run: npm run compat:module 102 | 103 | publish: 104 | <<: *nodelts 105 | steps: 106 | - *workspace 107 | - run: npm run build 108 | - run: 109 | name: NPM auth 110 | command: echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > ${HOME}/.npmrc 111 | - run: 112 | name: NPM publish 113 | command: ./scripts/circleci-npm-publish.sh 114 | 115 | workflows: 116 | version: 2 117 | fetch-mock-ci-cd: 118 | jobs: 119 | - checkout_code: *triggerable-by-tag 120 | - lint: 121 | <<: *triggerable-by-tag 122 | requires: 123 | - checkout_code 124 | - build: 125 | <<: *triggerable-by-tag 126 | requires: 127 | - checkout_code 128 | - test: 129 | <<: *triggerable-by-tag 130 | requires: 131 | - build 132 | - typecheck: 133 | <<: *triggerable-by-tag 134 | <<: *after-build-lint 135 | - test-browser: 136 | <<: *triggerable-by-tag 137 | <<: *after-build-test-lint 138 | - test-node18: 139 | <<: *triggerable-by-tag 140 | <<: *after-build-test-lint 141 | - test-jest: 142 | <<: *triggerable-by-tag 143 | <<: *after-build-test-lint 144 | - module-compat: 145 | <<: *triggerable-by-tag 146 | <<: *after-build-lint 147 | - publish: 148 | requires: 149 | - test-node18 150 | - test-browser 151 | - test-jest 152 | - build 153 | - test 154 | - lint 155 | - module-compat 156 | - typecheck 157 | filters: 158 | branches: 159 | ignore: /.*/ 160 | tags: 161 | only: /^[\w-]+-v?\d+\.\d+\.\d+(?:-(beta|alpha)\.\d+)?$/ 162 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_patterns: 2 | - docs 3 | - test/specs 4 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | # Review gh actions docs if you want to further define triggers, paths, etc 8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 9 | 10 | jobs: 11 | build: 12 | name: Build Docusaurus 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: 18 21 | cache: npm 22 | - name: Install dependencies 23 | run: npm ci 24 | - name: Test build website 25 | run: npm run build -w docs 26 | - name: Upload Build Artifact 27 | uses: actions/upload-pages-artifact@v3 28 | with: 29 | path: docs/build 30 | 31 | deploy: 32 | name: Deploy to GitHub Pages 33 | needs: build 34 | 35 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 36 | permissions: 37 | pages: write # to deploy to Pages 38 | id-token: write # to verify the deployment originates from an appropriate source 39 | 40 | # Deploy to the github-pages environment 41 | environment: 42 | name: github-pages 43 | url: ${{ steps.deployment.outputs.page_url }} 44 | 45 | runs-on: ubuntu-latest 46 | steps: 47 | - name: Deploy to GitHub Pages 48 | id: deployment 49 | uses: actions/deploy-pages@v4 50 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: release-please 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | release-please: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: googleapis/release-please-action@v4 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/test-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Test deployment 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | # Review gh actions docs if you want to further define triggers, paths, etc 8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 9 | 10 | jobs: 11 | test-deploy: 12 | name: Test deployment 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: 18 21 | cache: npm 22 | 23 | - name: Install dependencies 24 | run: npm ci 25 | - name: Test build website 26 | run: npm run build -w docs 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # tooling artefacts 4 | /docs/.sass-cache/ 5 | .eslintcache 6 | npm-debug.log 7 | 8 | # test artifacts 9 | coverage/ 10 | /test/fixtures/built-sw.js 11 | .nyc_output 12 | 13 | # built files 14 | /docs/fetch-mock/dist/ 15 | /packages/**/dist 16 | 17 | import-compat/*.js 18 | import-compat/*.mjs 19 | test-results 20 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit "$1" 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages/fetch-mock": "12.5.2", 3 | "packages/vitest": "0.2.13", 4 | "packages/jest": "0.2.15", 5 | "packages/codemods": "0.1.3" 6 | } 7 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | ISSUES NOT ADHERING TO THESE RULES WILL BE CLOSED 2 | If you want your issue addressed, please stick to the rules 3 | 4 | Please read the documentation before raising an issue 5 | - [Configuration](http://www.wheresrhys.co.uk/fetch-mock/#usageconfiguration) 6 | - [API](http://www.wheresrhys.co.uk/fetch-mock/#api-mockingmock) 7 | - [Troubleshooting](http://www.wheresrhys.co.uk/fetch-mock/#troubleshootingtroubleshooting) 8 | 9 | If your issue is about types being out of date please submit a PR. 10 | I'm happy to include types in this library, but I am not a typescript user 11 | but am not prepared to invest my time writing them. 12 | 13 | Be clear and precise about what the issue is. Take the time to include details of 14 | what's wrong, including the version of fetch-mock you're using and which environment 15 | it's running in. 16 | - Bad: 'It doesn't work', 'It conflicts with x', 'I get an error' 17 | - Good: 'When using webpack 4 and babel 7 I get an `Error: undefined array at line 191` 18 | during compilation' 19 | 20 | Provide code that recreates the issue you're having. Ideally either 21 | - recreate the problem with a failing test and submit a PR 22 | - recreate the problem in a code sharing platform, e.g. [repl.it](https://repl.it/languages/nodejs) 23 | - create a repo with a minimal test case using your build/test setup, and 24 | link to this in the issue 25 | 26 | The above may seem like a lot of work, but the easier you make it for me to 27 | recreate the bug, the sooner it'll get fixed. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rhys Evans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fetch-mock monorepo 2 | 3 | Where fetch-mock and a growing suite of utilities aimed at different testing frameworks lives. 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/blog/2024-07-20-new-beginnings.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: A new beginning for fetch-mock 3 | description: What's changing, and why 4 | slug: new-beginning 5 | authors: 6 | - name: Rhys Evans 7 | title: fetch-mock maintainer 8 | url: https://www.wheresrhys.co.uk 9 | hide_table_of_contents: false 10 | --- 11 | 12 | Long time users of fetch-mock may have noticed two things recently 13 | 14 | 1. This documentation website has had a makeover 15 | 2. I'm actually maintaining it again 16 | 17 | These two things are closely related. Allow me to explain 18 | 19 | ## Why I stopped maintaining fetch-mock 20 | 21 | As well as the perennial issue familiar to open source maintainers - working long hours for no pay, and not every user being particularly considerate in their feedback - I was also quite frustrated with the node.js and testing landscape. 22 | 23 | ECMAScript modules were slowly landing, and causing a lot of disruption and conflicts with different toolchains. Increasingly bug reports were about these conflicts in the wider ecosystem, rather than my code specifically. Really not a lot of fun to fix. Additionally, choices I'd made in fetch-mock's API design predated the arrival of the testing leviathan that is Jest. In trying to play nicely with Jest I found myself implementing hack after hack, and still being at the mercy of changes to Jest's internals. And don't even get me started on the demands to support typescript. 24 | 25 | I wasn't being paid and I increasingly felt like I wasn't maintaining a tool, rather piloting a rickety, outdated craft through choppy waters. 26 | 27 | Who needs that in their life? 28 | 29 | ## Why I'm back 30 | 31 | I've been unemployed for the last ~4 months, taking stock of my career and where it might head next. Early on in this lull the people from [TEA protocol](https://app.tea.xyz/) got in touch. It's a new way of funding open source and, while I am sceptical that it will catch on (though I hope I'm wrong), it did get me thinking a bit more about open source and fetch-mock. After 3 months of not touching a text editor I figured I also needed to be careful of not getting too rusty; a conversation with a friend uncovered that I'd forgotten the keyboard shortcut to clear the terminal. 32 | 33 | ## What I'm working on 34 | 35 | I began by setting out some high level principles to guide the work. 36 | 37 | 1. Be modern - support ESM, native global fetch & types by default. Pay as little attention to supporting other patterns as possible. 38 | 2. Avoid making API choices that conflict with current, or future, testing toolchains. Remove any APIs that already conflict. 39 | 3. Allow users to use fetch-mock in a way that is idiomatic to their choice of toolchain 40 | 41 | Guided by these I came up with a plan of attack: 42 | 43 | ### 1. Migrate everything to ESM and to global native fetch 44 | 45 | This was released as fetch-mock@10 about a month ago 46 | 47 | ### 2. Turn fetch-mock into a monorepo 48 | 49 | This makes it possible to publish multiple packages aimed at different use cases and environments. I now use conventional commits and release-please, which took a while to get right. 50 | 51 | I did this last week 52 | 53 | ### 3. Publish a @fetch-mock/core library 54 | 55 | This contains only the functionality that I'm confident other testing tools won't implement, e.g. it does not contain functionality for actually replacing `fetch` with a mock implementation; testing libraries generally have their own APIs for doing that. 56 | 57 | I did this a few days ago 58 | 59 | ### 4. Publish a suite of @fetch-mock libraries 60 | 61 | Each will be targeted at a particular toolchain, such as @fetch-mock/standalone, @fetch-mock/jest, @fetch-mock/vitest... 62 | 63 | I've not started on this yet. 64 | 65 | ## Why the new website 66 | 67 | Two reasons 68 | 69 | 1. With the new @fetch-mock suite there'll be a lot more to document, and there'll also need to be very clear separation between legacy (fetch-mock) and modern (@fetch-mock) documentation. 70 | 2. Static site generators have come a long way since I first put the fetch-mock site together, so worth looking again at the available tools. 71 | 72 | So you are now reading from a new site I put together with docusaurus. I tried astro/starlight too, but it doesn't yet support having multiple different documentation subsites hosted within a single site. Docusaurus does this very well. 73 | 74 | While @fetch-mock/core is not really intended for direct use, you _could_ e.g. do something like this in jest. 75 | 76 | ```js 77 | import fetchMock from '@fetch-mock/core' 78 | jest.mock(global, 'fetch', fetchMock.fetchHandler) 79 | 80 | it('works just fine', () => { 81 | fetchMock.route('http://here.com', 200); 82 | await expect (fetch('http://here.com')).resolves 83 | }) 84 | ``` 85 | 86 | I'd be very happy if people could start giving the library and its docs an experimental little whirl. 87 | -------------------------------------------------------------------------------- /docs/blog/2024-09-24-fetch-mock-12.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: fetch-mock@12 3 | description: A modern, more future proof, implementation 4 | slug: introducing-core 5 | authors: 6 | - name: Rhys Evans 7 | title: fetch-mock maintainer 8 | url: https://www.wheresrhys.co.uk 9 | hide_table_of_contents: false 10 | --- 11 | 12 | A couple of months ago [I wrote about @fetch-mock/core](https://www.wheresrhys.co.uk/fetch-mock/blog/introducing-core), intended to be a library not menat for direct use by users, but instead wrapped by libraries that implemented APIs more idiomatic for use alongside frameworks such as jest or vitest. 13 | 14 | But while implementing @fetch-mock/jest and @fetch-mock/vitest, I relaised something: the clashes between jest and fetch-mock-jest (the wrapper I wrote a few years ago for fetch-mock) were largely due to 15 | a) bad decisions about the design of fetch-mock-jest, which I can back away from 16 | b) parts of the fetch-mock API which ahd previously imitated mocha's API naming conventions 17 | 18 | By removing those two flaws, in principle there's nothing impossible about having a library that both has a good, user-friendly public API intended for direct use _and_ that lends itself to being wrapped for idiomatic use alongside a testing framework. 19 | 20 | So while writing @fetch-mock/jest and @fetch-mock/vitest, I have also added a few methods to @fetch-mock/core that make it easier for direct use, and will soon rename it to fetch-mock, publishing it as version 12. While these API additions are more user friendly than the previous low-level API, it's still fairly sparse. Please raise an issue if you think any methods should be added. 21 | 22 | To make it easier to migrate from fetch-mock@11 and below I've been working on a new [@fetch-mock/codemods](https://www.npmjs.com/package/@fetch-mock/codemods) library. If you'd like to try it out to migrate from fetch-mock to @fetch-mock/core, I'd be grateful of the feedback. 23 | 24 | It'll take a little while to restructure the documentation and build pipeline, but hopefully the new fetch-mock will be released in October. 25 | -------------------------------------------------------------------------------- /docs/docs/API/CallHistory.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Call history 6 | 7 | `fetchMock.callHistory` is an object used to record the history of all calls handled by `fetchMock.fetchHandler`. 8 | 9 | ## CallLog schema 10 | 11 | Calls are recorded, and returned, in a standard format with the following properties: 12 | 13 | - **arguments** `[string|Request,Object]` - the original arguments passed in to `fetch` 14 | - **url** `{string}` - The url being fetched 15 | - **options** `{NormalizedRequestOptions}` - The options passed in to the fetch (may be derived from a `Request` if one was used) 16 | - **request** `{Request}` - The `Request` passed to fetch, if one was used 17 | - **route** `{Route}` - The route used to handle the request 18 | - **response** `{Response}` - The `Response` returned to the user 19 | - **expressParams** `{Object.}` - Any express parameters extracted from the `url` 20 | - **queryParams** `{URLSearchParams}` - Any query parameters extracted from the `url` 21 | - **pendingPromises** `{Promise[]} ` - An internal structure used by the `.flush()` method documented below 22 | 23 | ## Filtering 24 | 25 | Most of `fetchMock.callHistory`'s methods take two arguments — `(filter, options)` — which allow `fetch` calls to be extracted and examined. 26 | 27 | ### filter 28 | 29 | Filter calls to `fetch` using one of the following criteria: 30 | 31 | #### undefined 32 | 33 | Retrieve all calls made to `fetch` 34 | 35 | #### true / "matched" 36 | 37 | Retrieve all calls to `fetch` matched by some route defined by `fetch-mock`. The string `'matched'` can be used instead of `true` to make tests more readable 38 | 39 | #### false / "unmatched" 40 | 41 | Retrieve all calls to `fetch` not matched by some route defined by `fetch-mock`, i.e. calls that are handled by `.catch()`. The string `'unmatched'` can be used instead of `false` to make tests more readable 42 | 43 | #### name 44 | 45 | `{String}` 46 | 47 | Retrieve all calls to `fetch` matched by a particular named route. 48 | 49 | #### matcher 50 | 51 | `{String|RegExp|function|Object}` 52 | Any matcher compatible with the [route api](/fetch-mock/docs/API/route/matcher) can be passed in to filter the calls arbitrarily. 53 | 54 | ### options 55 | 56 | `{Object}` 57 | 58 | An options object compatible with the [route api](/fetch-mock/docs/API/route/options) to be used to filter the list of calls further. 59 | 60 | ## Methods 61 | 62 | `fetchMock.callHistory` exposes the following methods. 63 | 64 | > Note that fetch calls made using `(url, options)` pairs are added synchronously, but calls using a `Request` are added asynchronously. This is because when a `Request` is used access to many of its internals is via asynchronous methods, while for an options object they can be read directly. In general it's best to `await` your application code to complete before attempting to access call history. 65 | 66 | ### .recordCall(callLog) 67 | 68 | For internal use. 69 | 70 | ### .calls(filter, options) 71 | 72 | Returns an array of all calls matching the given `filter` and `options`. Each call is returned as a `CallLog`. 73 | 74 | ### .called(filter, options) 75 | 76 | Returns a Boolean indicating whether any calls to `fetch` matched the given `filter` and `options` 77 | 78 | ### .lastCall(filter, options) 79 | 80 | Returns the `CallLog` for the last call to `fetch` matching the given `filter` and `options`. 81 | 82 | ### .done(routeNames) 83 | 84 | Returns a Boolean indicating whether `fetch` was called the expected number of times (or has been called at least once if `repeat` is not defined for the route). It does not take into account whether the `fetches` completed successfully. 85 | 86 | A single name or an array of names can be passed in the `routeNames` parameter to check only certain routes. If no routeNames are passed it runs over all routes. 87 | 88 | ### .flush(waitForBody) 89 | 90 | Returns a `Promise` that resolves once all fetches handled by fetch-mock have resolved 91 | 92 | Useful for testing code that uses `fetch` but where the returned promise cannot be accessed from your tests. 93 | 94 | If `waitForBody` is `true`, the promise will wait for all body parsing methods (`res.json()`, `res.text()`, etc.) to resolve too. 95 | -------------------------------------------------------------------------------- /docs/docs/API/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 2, 3 | "collapsible": false, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/API/mocking-and-spying.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Mocking and spying 6 | 7 | These methods allow mocking or spying on the `fetch` implementation used by your application. 8 | 9 | ## fetchHandler 10 | 11 | `fetchMock.fetchHandler(url, requestInit)` 12 | 13 | A mock implementation of `fetch`. 14 | 15 | By default it will error. In order to return responses `.route()`, `.catch()` and other routing methods of `fetchMock` must first be used to add routes to its internal router. 16 | 17 | All calls made using `fetchMock.fetchHandler` are recorded in `fetchMock.callHistory`. 18 | 19 | You can either pass `fetchMock.fetchHandler` into your choice of mocking library or use the methods below to mock global `fetch`. 20 | 21 | ## mockGlobal() 22 | 23 | Replaces `globalThis.fetch` with `fm.fetchHandler` 24 | 25 | ## unmockGlobal() 26 | 27 | Restores `globalThis.fetch` to its original state 28 | 29 | ## spy(matcher, name) 30 | 31 | Falls back to the `fetch` implementation set in `fetchMock.config.fetch` for a specific route (which can be named). 32 | 33 | When no arguments are provided it will fallback to the native fetch implementation for all requests, similar to `.catch()`. 34 | 35 | ## spyGlobal() 36 | 37 | Equivalent to calling `.mockGlobal()` followed by `.spy()` 38 | -------------------------------------------------------------------------------- /docs/docs/API/resetting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Resetting 6 | 7 | The methods below can be used to restore some or all of fetchMock's behaviour toits default state. 8 | 9 | ## .hardReset(options) 10 | 11 | Removes all the behaviour and state that's been added so far. It runs `.removeRoutes()`, `.clearHistory()` and `.unmockGlobal()`. Pass `{includeSticky: true}` to force even sticky routes to be removed. 12 | 13 | ## .removeRoutes(options) 14 | 15 | This method removes some or all of the routes that have been added to fetch mock. It accepts the following options. 16 | 17 | ### names 18 | 19 | An array of named routes to remove. If no value is passed then all routes compatible with the other options passed are removed. 20 | 21 | ### includeSticky 22 | 23 | A boolean indicating whether or not to remove sticky routes. 24 | 25 | ### includeFallback 26 | 27 | A boolean indicating whether or not to remove the fallback route (added using `.catch()`) 28 | 29 | ## .clearHistory() 30 | 31 | Clears all data recorded for `fetch`'s calls. 32 | 33 | ## .unmockGlobal() 34 | 35 | Restores global `fetch` to its original state if `.mockGlobal()` or `.spyGlobal()` have been used . 36 | 37 | ## .createInstance() 38 | 39 | Can be used to create a standalone instance of fetch mock that is completely independent of other instances. 40 | 41 | In older environments - where `fetch` was not a global and test files often ran in a shared process - this was very useful for increasing parallelisation. But in more modern setups - global `fetch` and isolated processes per test file - it's less relevant. 42 | 43 | It can however be used as an alternative to `fetchMock.removeRoutes().clearHistory()` by creating a completely new fetchMock instance and swapping this for the previously used one. 44 | 45 | It can also be used to "fork" a previous instance as routes are cloned from one instance to another. However call history is always reset for new instances. 46 | 47 | ```js 48 | fetchMock.route('http://my.site', 200); 49 | fetchMock.fetchHandler('http://my.site'); 50 | 51 | const newFM = fetchMock.createInstance(); 52 | 53 | newFM.routes.routes; // Array[1] 54 | newFM.callHistory.calls(); // Array[0] 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/docs/API/route/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 2.5, 3 | "collapsible": false, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/API/route/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | sidebar_label: .route() 4 | --- 5 | 6 | # route() 7 | 8 | `fetchMock.route(matcher, response, options)` 9 | 10 | Adds a route to `fetchHandler`'s router. A route is a combination of 11 | 12 | - one or more rules for deciding whether a particular fetch request should be handled by the route 13 | - configuration for generating an appropriate response 14 | 15 | It returns the fetchMock instance, so is chainable e.g. `fetchMock.route('a', 200).route('b', 301)` 16 | 17 | ## Parameters 18 | 19 | ### matcher 20 | 21 | `{String|Regex|Function|Object}` 22 | 23 | Determines which calls to `fetch` should be handled by this route. If multiple criteria need to be applied at once, e.g. matching headers and a uirl pattern, then an object containing multiple matchers may be used. 24 | 25 | ### response 26 | 27 | `{String|Object|Function|Promise|Response}` 28 | 29 | Response to send when a call is matched 30 | 31 | ### options 32 | 33 | `{Object|String}` 34 | 35 | More options to configure matching and response behaviour. Alternatively, when a `String` is provided it will be used as a name for the route (used when inspecting calls or removing routes). 36 | 37 | ## Alternate call patterns 38 | 39 | As well as the function signature described above, the following patterns are supported. 40 | 41 | ### Name as third parameter 42 | 43 | A string can be passed as the third parameter, and will be used as the route name. e.g. 44 | `fetchMock.route('*', 200, 'catch-all')` is equivalent to `fetchMock.route('*', 200, {name: 'catch-all'})`. 45 | 46 | ### Matchers on third parameter 47 | 48 | The matchers specified in the first parameter are merged internally with the options object passed as the third parameter. This means that it does not matter on which of these objects you specify options/matchers. 49 | 50 | This can be particularly useful for clearly and concisely expressing similar routes that only differ in e.g. different query strings or headers e.g. 51 | 52 | ```js 53 | fetchMock 54 | .route('http://my.site', 401) 55 | .route('http://my.site', 200, { headers: { auth: true } }); 56 | ``` 57 | 58 | ### Single options object 59 | 60 | Matchers, options and a response can be combined on a single options object passed into the first parameter, e.g. 61 | 62 | ```js 63 | fetchMock.route({ 64 | url: 'http://my.site', 65 | repeat: 2 66 | response: 200 67 | }) 68 | 69 | ``` 70 | 71 | ## Examples 72 | 73 | ### Strings 74 | 75 | ```js 76 | fetchMock 77 | .route('http://it.at.here/route', 200) 78 | .route('begin:http://it', 200) 79 | .route('end:here/route', 200) 80 | .route('path:/route', 200) 81 | .route('*', 200); 82 | ``` 83 | 84 | ### Complex Matchers 85 | 86 | ```js 87 | fetchMock 88 | .route(/.*\.here.*/, 200) 89 | .route((url, opts) => opts.method === 'patch', 200) 90 | .route('express:/:type/:id', 200, { 91 | params: { 92 | type: 'shoe', 93 | }, 94 | }) 95 | .route( 96 | { 97 | headers: { Authorization: 'Bearer 123' }, 98 | method: 'POST', 99 | }, 100 | 200, 101 | ); 102 | ``` 103 | 104 | ### Responses 105 | 106 | ```js 107 | fetchMock 108 | .route('*', 'ok') 109 | .route('*', 404) 110 | .route('*', {results: []}) 111 | .route('*', {throw: new Error('Bad kitty')}) 112 | .route('*', new Promise(res => setTimeout(res, 1000, 404))) 113 | .route('*', (url, opts) => { 114 | status: 302, 115 | headers: { 116 | Location: url.replace(/^http/, 'https') 117 | }, 118 | })) 119 | ``` 120 | 121 | ### End to end example 122 | 123 | ```js 124 | fetchMock.route('begin:http://it.at.here/api', 403).route( 125 | { 126 | url: 'begin:http://it.at.here/api', 127 | headers: { 128 | authorization: 'Basic dummy-token', 129 | }, 130 | }, 131 | 200, 132 | ); 133 | 134 | callApi('/endpoint', 'dummy-token').then((res) => { 135 | expect(res.status).to.equal(200); 136 | }); 137 | ``` 138 | -------------------------------------------------------------------------------- /docs/docs/API/route/options.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # options 6 | 7 | An object containing further options for configuring mocking behaviour. 8 | 9 | ## Options 10 | 11 | ### name 12 | 13 | `{String}` 14 | 15 | A unique string naming the route. Used to subsequently retrieve references to the calls handled by it. Only needed for advanced use cases. 16 | 17 | ### response 18 | 19 | When using pattern 3. above, `response` can be passed as a property on the options object. See the [response documentation](/fetch-mock/docs/API/route/response) for valid values. 20 | 21 | ### repeat 22 | 23 | `{Int}` 24 | 25 | Limits the number of times the route will be used to respond. If the route has already been called `repeat` times, the call to `fetch()` will fall through to be handled by any other routes defined (which may eventually result in an error if nothing matches it). 26 | 27 | ### delay 28 | 29 | `{Int}` 30 | 31 | Delays responding for the number of milliseconds specified. 32 | 33 | ### waitFor 34 | 35 | `{String|[String]}` 36 | Useful for testing race conditions. Use the name of another route (or a list of names), and this route will only respond after that one has. (Note, this does not wait for the body - only the initial response payload). 37 | 38 | ### sticky 39 | 40 | `{Boolean}` 41 | 42 | Avoids a route being removed when `removeRoutes()` is called. 43 | 44 | ### sendAsJson 45 | 46 | `{Boolean}` 47 | 48 | See [global configuration](/fetch-mock/docs/Usage/configuration) 49 | 50 | ### includeContentLength 51 | 52 | `{Boolean}` 53 | 54 | See [global configuration](/fetch-mock/docs/Usage/configuration) 55 | -------------------------------------------------------------------------------- /docs/docs/API/route/response.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # response 6 | 7 | Configures the http response returned by `fetchHandler`. Unless otherwise stated, all responses have a `200` status 8 | 9 | ## Response 10 | 11 | `{Response}` 12 | 13 | A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response) instance to return unaltered. 14 | 15 | ## Status code 16 | 17 | `{Int}` 18 | 19 | Returns a `Response` with the given status code, e.g. `200`. The response's `statusText` will also be set to the [default value corresponding to the status](https://fetch.spec.whatwg.org/#dom-response-statustext). 20 | 21 | ## String 22 | 23 | `{String}` 24 | 25 | Returns a 200 `Response` with the string as the response body e.g. `"..."` 26 | 27 | ## Response config 28 | 29 | `{Object}` 30 | 31 | If the object _only_ contains properties from among those listed below it is used to configure a `Response` to return. 32 | 33 | ### body 34 | 35 | `{String|Object|BodyInit}` 36 | 37 | Set the `Response` body. This could be 38 | 39 | - a string e.g. `"Server responded ok"`, `{ token: 'abcdef' }`. 40 | - an object literal (see the `Object` section of the docs below). 41 | - Anything else that satisfies the specification for the [body parameter of new Response()](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#body). This currently allows instances of Blob, ArrayBuffer, TypedArray, DataView, FormData, ReadableStream, URLSearchParams, and String. 42 | 43 | ### status 44 | 45 | `{Int}` 46 | 47 | Sets the `Response` status e.g. `200` 48 | 49 | ### headers 50 | 51 | `{Object}` 52 | 53 | Sets the `Response` headers, e.g `{'Content-Type': 'text/html'}` 54 | 55 | ### redirectUrl 56 | 57 | `{String}` 58 | 59 | Sets the url from which the `Response` should claim to originate (to imitate followed directs). Will also set `redirected: true` on the response 60 | 61 | ### throws 62 | 63 | `{Error}` 64 | 65 | Forces `fetch` to return a `Promise` rejected with the value of `throws` e.g. `new TypeError('Failed to fetch')` 66 | 67 | ## Object 68 | 69 | `{Object}` 70 | 71 | Any object literal that does not match the schema for a response config will be converted to a `JSON` string and set as the response `body`. 72 | 73 | The `Content-Type: application/json` header will also be set on each response. To send JSON responses that do not set this header (e.g. to mock a poorly configured server) manually convert the object to a string first e.g. 74 | 75 | ```js 76 | fetchMock.route('http://a.com', JSON.stringify({ prop: 'value' })); 77 | ``` 78 | 79 | ## Promise 80 | 81 | `{Promise}` 82 | 83 | A `Promise` that resolves to any of the options documented above e.g. `new Promise(res => setTimeout(() => res(200), 50))` 84 | 85 | ## Function 86 | 87 | `{Function}` 88 | 89 | A function that is passed a [`CallLog`](/fetch-mock/docs/API/CallHistory#calllog-schema) and returns any of the options documented above (including `Promise`). 90 | 91 | ### Examples 92 | 93 | - `({url, options}) => options.headers.Authorization ? 200 : 403` 94 | - `({request}) => request.headers.get('Authorization') ? 200 : 403` 95 | -------------------------------------------------------------------------------- /docs/docs/Usage/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 1, 3 | "collapsible": false, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/Usage/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Configuration 6 | 7 | On any `fetch-mock` instance, set configuration options directly on the `fetchMock.config` object. e.g. 8 | 9 | ```js 10 | const fetchMock = require('fetch-mock'); 11 | fetchMock.config.matchPartialBody = false; 12 | ``` 13 | 14 | ## Available options 15 | 16 | Options marked with a `†` can also be overridden for individual calls to `.route(matcher, response, options)` by setting as properties on the `options` parameter 17 | 18 | ### includeContentLength 19 | 20 | `{Boolean}` default: `true` 21 | 22 | Sets a `Content-Length` header on each response, with the exception of responses whose body is a `FormData` or `ReadableStream` instance as these are hard/impossible to calculate up front. 23 | 24 | ### matchPartialBody 25 | 26 | `{Boolean}` default: `false` 27 | 28 | Match calls that only partially match a specified body json or FormData. 29 | 30 | ### allowRelativeUrls 31 | 32 | `{Boolean}` default: `false` 33 | 34 | `fetch` in node.js does not support relative urls. For the purposes of testing browser modules in node.js it is possible to use this flag to avoid errors. However, you may prefer to use [jsdom](https://www.npmjs.com/package/jsdom) or similar to set `globalThis.location` to an instance of the DOM class `Location`. 35 | 36 | ### Custom fetch implementations 37 | 38 | `fetch`, `Headers`, `Request`, `Response` can all be set on the configuration object, allowing fetch-mock to mock any implementation of `fetch`, e.g. `node-fetch`. e.g. 39 | 40 | ```js 41 | import { default as fetch, Headers, Request, Response } from 'node-fetch'; 42 | 43 | import fetchMock from 'fetch-mock'; 44 | 45 | fetchMock.config = Object.assign(fetchMock.config, { 46 | Request, 47 | Response, 48 | Headers, 49 | fetch, 50 | }); 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/docs/Usage/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Installation 6 | 7 | Install fetch-mock using 8 | 9 | ```bash 10 | npm install --save-dev fetch-mock 11 | ``` 12 | 13 | fetch-mock supports both [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) and [commonjs](https://requirejs.org/docs/commonjs.html). The following should work in most environments. 14 | 15 | ## ES modules 16 | 17 | ```js 18 | import fetchMock from 'fetch-mock'; 19 | ``` 20 | 21 | ## Commonjs 22 | 23 | ```js 24 | const fetchMock = require('fetch-mock'); 25 | ``` 26 | 27 | ## Using alongside other testing frameworks 28 | 29 | When using one of the following frameworks consider using the appropriate wrapper library, which modify/extend fetch-mock to make using it more idiomatic to your testing environment e.g. adding methods equivalent to Jest's `mockRestore()` etc. 30 | 31 | - Jest - [@fetch-mock/jest](/fetch-mock/docs/wrappers/jest) 32 | - Vitest - [@fetch-mock/vitest](/fetch-mock/docs/wrappers/vitest) 33 | 34 | ## Deno support 35 | 36 | Import fetch-mock, or the wrappers above, using the `npm:` prefix, e.g. 37 | 38 | ```js 39 | import fetchMock from 'npm:fetch-mock@12.3.0'; 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/docs/Usage/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Quickstart 6 | 7 | ## Import fetch-mock 8 | 9 | Use one of the following 10 | 11 | ```js 12 | import fetchMock from 'fetch-mock'; 13 | ``` 14 | 15 | ```js 16 | const fetchMock = require('fetch-mock'); 17 | ``` 18 | 19 | ## Mocking fetch 20 | 21 | Use the following to replace `fetch` with fetch-mock's implementation of the `fetch` API. 22 | 23 | ```js 24 | fetchMock.mockGlobal(); 25 | ``` 26 | 27 | ## Setting up your route 28 | 29 | The commonest use case is `fetchMock.route(matcher, response, name)`, where `matcher` is an exact url to match, and `response` is a status code, string or object literal. Optionally you can pass a name for your route as the third parameter. There are many other options for defining your matcher or response, [route documentation](/fetch-mock/docs/API/route/). 30 | 31 | You can also use `fetchMock.once()` to limit to a single call or `fetchMock.get()`, `fetchMock.post()` etc. to limit to a method. 32 | 33 | All these methods are chainable so you can easily define several routes in a single test. 34 | 35 | ```js 36 | fetchMock 37 | .get('http://good.com/', 200, 'good get') 38 | .post('http://good.com/', 400, 'good post') 39 | .get('http://bad.com/', 500, 'bad get'); 40 | ``` 41 | 42 | ## Analysing calls to your mock 43 | 44 | You can use the names you gave your routes to check if they have been called. 45 | 46 | - `fetchMock.called(name)` reports if any calls were handled by your route. If you just want to check `fetch` was called at all then do not pass in a name. 47 | - `fetchMock.lastCall(name)` will return a CallLog object that will give you access to lots of metadata about the call, including the original arguments passed in to `fetch`. 48 | - `fetchMock.done()` will tell you if `fetch` was called the expected number of times. 49 | 50 | ```js 51 | assert(fetchMock.called('good get')); 52 | assertEqual(fetchMock.lastCall('good get').query['search'], 'needle'); 53 | ``` 54 | 55 | ## Tearing down your mock 56 | 57 | - `fetchMock.clearHistory()` clears all the records of calls made to `fetch`. 58 | - `fetchMock.removeRoutes()` removes all the routes set up. 59 | - `fetchMock.unmockGlobal()` resets `fetch` to its original implementation. 60 | 61 | ## Example 62 | 63 | Example with Node.js: suppose we have a file `make-request.js` with a function that calls `fetch`: 64 | 65 | ```js 66 | export async function makeRequest() { 67 | const res = await fetch('http://example.com/my-url', { 68 | headers: { 69 | user: 'me', 70 | }, 71 | }); 72 | return res.json(); 73 | } 74 | ``` 75 | 76 | We can use fetch-mock to mock `fetch`. In `mocked.js`: 77 | 78 | ```js 79 | import { makeRequest } from './make-request'; 80 | import fetchMock from 'fetch-mock'; 81 | 82 | // Mock the fetch() global to return a response 83 | fetchMock.mockGlobal().get( 84 | 'http://httpbin.org/my-url', 85 | { hello: 'world' }, 86 | { 87 | delay: 1000, // fake a slow network 88 | headers: { 89 | user: 'me', // only match requests with certain headers 90 | }, 91 | }, 92 | ); 93 | 94 | const data = await makeRequest(); 95 | console.log('got data', data); 96 | 97 | // Unmock 98 | fetchMock.unmockGlobal(); 99 | ``` 100 | 101 | Result: 102 | 103 | ```bash 104 | $ node mocked.js 105 | 'got data' { hello: 'world' } 106 | ``` 107 | -------------------------------------------------------------------------------- /docs/docs/Usage/requirements.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0.5 3 | --- 4 | 5 | # Requirements 6 | 7 | fetch-mock requires the following to run: 8 | 9 | - Either 10 | - [Node.js](https://Node.js.org/) 18+ 11 | - A modern browser implementing the `fetch` API. 12 | - [npm](https://www.npmjs.com/package/npm) (normally comes with Node.js) 13 | 14 | For usage in older versions of Node.js or older browsers consider [using an older version of fetch-mock](/fetch-mock/docs/legacy-api). 15 | 16 | If using node-fetch in your application fetch-mock@12 and above may work for you, but the fetch-mock test suite does not run against node-fetch, so it may be safer to use an [older version of fetch-mock](http://localhost:3000/fetch-mock/docs/legacy-api/) that is tested against node-fetch and is less likely to introduce breaking changes. 17 | 18 | ## Usage with react-native 19 | 20 | As react-native ships with a non-spec-compliant version of the URL and URLSearchParams classes, fetch-mock will not run in a react-native environment unless you also include [react-native-url-polyfill](https://www.npmjs.com/package/react-native-url-polyfill). You can either: 21 | 22 | 1. Include it in your application code. This will add approx 11kb of gzipped code to your bundle. 23 | 2. Include it in your test files only. If doing this, its recommended that you have some integration tests that _don't_ use fetch-mock in order to avoid inserting behaviour into your application that relies on the polyfill. 24 | -------------------------------------------------------------------------------- /docs/docs/Usage/upgrade-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Upgrade guide 3 | --- 4 | 5 | fetch-mock@12 has many breaking changes compared to fetch-mock@11: 6 | 7 | The [@fetch-mock/codemods](https://www.npmjs.com/package/@fetch-mock/codemods) library provides a tool that will attempt to fix most of these automatically, and its readme contains advice on which manual changes to make alongside these. 8 | 9 | ## Summary of changes 10 | 11 | ### Uses global, native `fetch` implementation in all environments 12 | 13 | Previously this was true in browsers, but in node.js the node-fetch library was used. 14 | 15 | ### `.mock()` method removed 16 | 17 | This combined adding a route with mocking the global instance of `fetch`. These are now split into 2 methods: `.route()` and `.mockGlobal()`. 18 | 19 | ### Routing convenience methods no longer mock `fetch` 20 | 21 | Similar to the last point, `.once()`, `.get()`, `.post()`, `getOnce()`, `postOnce()` and all other routing convenience methods no longer mock the global instance of `fetch`, and you will need to call `.mockGlobal()` explicitly. 22 | 23 | ### Reset methods changed 24 | 25 | `.reset()`, `.restore()`, `.resetBehavior()` and `.resetHistory()` have been removed and replaced with [methods that are more granular and clearly named](/fetch-mock/docs/API/resetting). Note that the [jest](/fetch-mock/docs/wrappers/jest) and [vitest](/fetch-mock/docs/wrappers/vitest) wrappers for fetch-mock still implement `.mockClear()`, `mockReset()` and `mockRestore()`. 26 | 27 | ### Call history methods changed 28 | 29 | The filtering behaviour has been rewritten around named routes, and methods return CallLog objects that contain far more metadata about the call. All methods, including, `done()` shoudl now be called on `fetchMock.callHistory` (not `fetchMock`). [Call history docs](/fetch-mock/docs/API/CallHistory). 30 | 31 | ### Relative URLs not permitted by default in Node.js 32 | 33 | A consequence of shifting to use the native implementation of the URL class is that relative URLs are no longer permissable when running tests in node.js, but [this can easily be enabled](https://www.wheresrhys.co.uk/fetch-mock/docs/Usage/configuration#allowrelativeurls). 34 | 35 | ### Some convenience routing methods removed 36 | 37 | `getOnce()` and `getAnyOnce()` have been removed, but the behaviour can still be implemented by the user as follows: 38 | 39 | - `getOnce()` -> `get(url, response, {repeat: 1})` 40 | - `getAnyOnce()` -> `get('*', response, {repeat: 1})` 41 | 42 | The same is true for `postOnce()`, `deleteOnce()` etc. 43 | 44 | ### Options removed 45 | 46 | - `overwriteRoutes` - this reflects that multiple routes using the same underlying matcher but different options no longer throw an error. If you still need to overwrite route behaviour (equivalent to `overwriteRoutes: true`) use [`modifyRoute()` or `removeRoute()`](/fetch-mock/docs/API/more-routing-methods#) 47 | - `warnOnFallback` - given the improved state of node.js debugging tools compared to when fetch-mock was first written, this debugging utilty has been removed. 48 | - `sendAsJson` - fetch-mock@12 implements streams more robustly than previous options, so the user no longer needs to flag when an object response should be converted to JSON. 49 | - `fallbackToNetwork` - The [`spyGlobal()` method](/fetch-mock/docs/API/mocking-and-spying#spyglobal) should now be used. 50 | 51 | ### `sandbox()` method removed 52 | 53 | This was principally used when mocking node-fetch referenced as a local variable. Given that `fetch` is now available as a native global it's less useful and has been removed. If necessary to mock a local instance of node-fetch use [`.fetchHandler`](/fetch-mock/docs/API/mocking-and-spying#fetchhandler) 54 | 55 | ### Types completely written 56 | 57 | There are many changes, both in the naming and structuring of types. Refer to the errors generated by your toolchain to update these. 58 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | sidebar_position: 0 4 | --- 5 | 6 | fetch-mock is the most comprehensive library for mocking the fetch API. 7 | 8 | It allows mocking http requests made using [fetch](https://fetch.spec.whatwg.org/) or a library imitating its api, such as [node-fetch](https://www.npmjs.com/package/node-fetch). 9 | 10 | It supports most JavaScript environments, including browsers, Node.js, web workers and service workers. 11 | 12 | As well as shorthand methods for the simplest use cases, it offers a flexible API for customising all aspects of mocking behaviour. 13 | 14 | > **For documentation for fetch-mock v11 and below visit the [Legacy API docs](/fetch-mock/docs/legacy-api)** 15 | 16 | ## Examples 17 | 18 | ```js 19 | import fetchMock from 'fetch-mock'; 20 | fetchMock.mockGlobal().route('http://example.com', 200); 21 | const res = await fetch('http://example.com'); 22 | assert(res.ok); 23 | ``` 24 | 25 | ```js 26 | import fetchMock from 'fetch-mock'; 27 | 28 | fetchMock 29 | .mockGlobal() 30 | .route({ 31 | express: '/api/users/:user' 32 | expressParams: {user: 'kenneth'} 33 | }, { 34 | userData: { 35 | email: 'kenneth@example.com' 36 | } 37 | }, 'userDataFetch'); 38 | 39 | const res = await fetch('http://example.com/api/users/kenneth'); 40 | assert(fetchMock.callHistory.called('userDataFetch')) 41 | const data = await res.json(); 42 | assertEqual(data.userData.email, 'kenneth@example.com') 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Inspection/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 2, 3 | "collapsible": true, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Inspection/done.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # .done(filter) 6 | 7 | Returns a Boolean indicating whether `fetch` was called the expected number of times (or has been called at least once if `repeat` is undefined for the route). It does not take into account whether the `fetches` completed successfully. 8 | 9 | ## Filter 10 | 11 | ### undefined / true 12 | 13 | Returns true if all routes have been called the expected number of times 14 | 15 | ### routeIdentifier 16 | 17 | `{String|RegExp|function}` 18 | 19 | All routes have an identifier: 20 | 21 | - If it's a named route, the identifier is the route's name 22 | - If the route is unnamed, the identifier is the `matcher` passed in to `.mock()` 23 | 24 | Returns true if the routes specified by the identifier has been called the expected number of times 25 | 26 | If several routes have the same matcher/url, but use [mocking options](/fetch-mock/docs/legacy-api/API/Mocking/Parameters/options), the recommended way to handle this is to [name each route](/fetch-mock/docs/legacy-api/API/Mocking/Parameters/options) and filter using those names 27 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Inspection/flush.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # .flush(waitForBody) 6 | 7 | Returns a `Promise` that resolves once all fetches handled by fetch-mock have resolved 8 | 9 | Useful for testing code that uses `fetch` but doesn't return a promise. 10 | 11 | If `waitForBody` is `true`, the promise will wait for all body parsing methods (`res.json()`, `res.text()`, etc.) to resolve too. 12 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Lifecycle/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 3, 3 | "collapsible": true, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Lifecycle/resetting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Resetting 6 | 7 | ## .restore() / .reset() 8 | 9 | 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. 10 | 11 | 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())` 12 | 13 | ## .resetHistory() 14 | 15 | Clears all data recorded for `fetch`'s calls. It _will not_ restore fetch to its default implementation 16 | 17 | `resetHistory()` is bound to fetchMock, and can be used directly as a callback e.g. `afterEach(fetchMock.resetHistory)` will work just fine. There is no need for `afterEach(() => fetchMock.resetHistory())` 18 | 19 | ## .resetBehavior() 20 | 21 | 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. 22 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Lifecycle/sandbox.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # .sandbox() 6 | 7 | Returns a function that can be used as a drop-in replacement for `fetch`. Pass this into your mocking library of choice. The function returned by `sandbox()` has all the methods of `fetch-mock` exposed on it and maintains its own state independent of other instances, so tests can be run in parallel. 8 | 9 | ```js 10 | fetchMock.sandbox().mock('http://domain.com', 200); 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/Parameters/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 1.5, 3 | "collapsible": true, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/Parameters/matcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # matcher 6 | 7 | Criteria for deciding which requests to mock. 8 | 9 | Note that if you use matchers that target anything other than the url string, you may also need to add a `name` to your matcher object so that a) you can add multiple mocks on the same url that differ only in other properties (e.g. query strings or headers) b) if you [inspect](/fetch-mock/docs/legacy-api/API/Inspection/inspecting-calls) the result of the fetch calls, retrieving the correct results will be easier. 10 | 11 | ## Argument values 12 | 13 | > Note that if using `end:` or an exact url matcher, fetch-mock ([for good reason](https://url.spec.whatwg.org/#url-equivalence)) is unable to distinguish whether URLs without a path end in a trailing slash or not i.e. `http://thing` is treated the same as `http://thing/` 14 | 15 | ### \* 16 | 17 | `{String}` 18 | 19 | Matches any url 20 | 21 | ### url 22 | 23 | `{String|URL}` 24 | Match an exact url. Can be defined using a string or a `URL` instance, e.g. `"http://www.site.com/page.html"` 25 | 26 | ### begin:... 27 | 28 | `{String}` 29 | Match a url beginning with a string, e.g. `"begin:http://www.site.com"` 30 | 31 | ### end:... 32 | 33 | `{String}` 34 | Match a url ending with a string, e.g. `"end:.jpg"` 35 | 36 | ### path:... 37 | 38 | `{String}` 39 | Match a url which has a given path, e.g. `"path:/posts/2018/7/3"` 40 | 41 | ### glob:... 42 | 43 | `{String}` 44 | Match a url using a glob pattern, e.g. `"glob:http://*.*"` 45 | 46 | ### express:... 47 | 48 | `{String}` 49 | Match a url that satisfies an [express style path](https://www.npmjs.com/package/path-to-regexp), e.g. `"express:/user/:user"` 50 | 51 | See also the `params` option before for matching on the values of express parameters. 52 | 53 | ### RegExp 54 | 55 | `{RegExp}` 56 | Match a url that satisfies a regular expression, e.g. `/(article|post)\/\d+/` 57 | 58 | ### Function 59 | 60 | `{Function}` 61 | Match if a function returns something truthy. The function will be passed the `url` and `options` `fetch` was called with. If `fetch` was called with a `Request` instance, it will be passed `url` and `options` inferred from the `Request` instance, with the original `Request` will be passed as a third argument. 62 | 63 | This can also be set as a `functionMatcher` in the [options parameter](/fetch-mock/docs/legacy-api/API/Mocking/Parameters/options), and in this way powerful arbitrary matching criteria can be combined with the ease of the declarative matching rules above. 64 | 65 | #### Examples 66 | 67 | - `(url, {headers}) => !!headers.Authorization` 68 | - `(_, _, request) => !!request.headers.get('Authorization')` 69 | 70 | ### Options Object 71 | 72 | `{Object}` 73 | 74 | The url and function matchers described above can be combined with other criteria for matching a request by passing an options object which may have one or more of the properties described in the documentation for the `options` parameter. 75 | 76 | In particular, **headers**, **query string parameters**, **request bodies** and **express parameter values** can all be used as matching criteria. 77 | 78 | #### Examples 79 | 80 | - `{url: 'end:/user/profile', headers: {Authorization: 'Basic 123'}}` 81 | - `{query: {search: 'abc'}, method: 'POST'}` 82 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/Parameters/options.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # optionsOrName 6 | 7 | Either 8 | 9 | - An object containing further options for configuring mocking behaviour. 10 | - A string, which will be used as the route's name 11 | 12 | ## General options 13 | 14 | ### name 15 | 16 | `{String}` 17 | 18 | A unique string naming the route. Used to subsequently retrieve references to the calls handled by it. Only needed for advanced use cases. 19 | 20 | ### response 21 | 22 | Instead of defining the response as the second argument of `mock()`, it can be passed as a property on the first argument. See the [response documentation](/fetch-mock/docs/legacy-api/API/Mocking/Parameters/response) for valid values. 23 | 24 | ### repeat 25 | 26 | `{Int}` 27 | 28 | Limits the number of times the route can be used. If the route has already been called `repeat` times, the call to `fetch()` will fall through to be handled by any other routes defined (which may eventually result in an error if nothing matches it) 29 | 30 | ### delay 31 | 32 | `{Int}` 33 | 34 | Delays responding for the number of milliseconds specified. 35 | 36 | ### sticky 37 | 38 | `{Boolean}` 39 | 40 | Avoids a route being removed when `reset()`, `restore()` or `resetBehavior()` are called. _Note - this does not preserve the history of calls to the route_ 41 | 42 | ### sendAsJson 43 | 44 | `{Boolean}` 45 | 46 | See [global configuration](/fetch-mock/docs/legacy-api/Usage/configuration) 47 | 48 | ### includeContentLength 49 | 50 | `{Boolean}` 51 | 52 | See [global configuration](/fetch-mock/docs/legacy-api/Usage/configuration) 53 | 54 | ### overwriteRoutes 55 | 56 | `{Boolean}` 57 | 58 | See [global configuration](/fetch-mock/docs/legacy-api/Usage/configuration) 59 | 60 | ## Options for defining matchers 61 | 62 | If multiple mocks use the same url matcher but use different options, such as `headers`, you will need to use the `overwriteRoutes: false` option. 63 | 64 | ### url 65 | 66 | `{String|RegExp}` 67 | 68 | Use any of the `String` or `RegExp` matchers described in the documentation fro the `matcher` parameter. 69 | 70 | ### functionMatcher 71 | 72 | `{Function}` 73 | 74 | Use a function matcher, as described in the documentation fro the `matcher` parameter. 75 | 76 | ### method 77 | 78 | `{String}` 79 | 80 | Match only requests using this http method. Not case-sensitive, e.g. `"get"`, `"POST"` 81 | 82 | ### headers 83 | 84 | `{Object|Headers}` 85 | 86 | Match only requests that have these headers set, e.g. `{"Accepts": "text/html"}` 87 | 88 | ### body 89 | 90 | `{Object}` 91 | 92 | Match only requests that send a JSON body with the exact structure and properties as the one provided here. 93 | 94 | Note that if matching on body _and_ using `Request` instances in your source code, this forces fetch-mock into an asynchronous flow _before_ it is able to route requests effectively. This means no [inspection methods](/fetch-mock/docs/legacy-api/API/Inspection/inspecting-calls) can be used synchronously. You must first either await the fetches to resolve, or `await fetchMock.flush()`. The popular library [Ky](https://github.com/sindresorhus/ky) uses `Request` instances internally, and so also triggers this mode. 95 | 96 | e.g.`{ "key1": "value1", "key2": "value2" }` 97 | 98 | ### matchPartialBody 99 | 100 | `{Boolean}` 101 | 102 | Match calls that only partially match a specified body json. See [global configuration](/fetch-mock/docs/legacy-api/Usage/configuration) for details. 103 | 104 | ### query 105 | 106 | `{Object}` 107 | 108 | Match only requests that have these query parameters set (in any order). Query parameters are matched by using Node.js [querystring](https://nodejs.org/api/querystring.html) module. In summary the bahaviour is as follows 109 | 110 | - strings, numbers and booleans are coerced to strings 111 | - arrays of values are coerced to repetitions of the key 112 | - all other values, including `undefined`, are coerced to an empty string 113 | The request will be matched whichever order keys appear in the query string. 114 | Any query parameters sent in the request which are not included in the keys of the object provided will be ignored. 115 | 116 | #### Examples 117 | 118 | - `{"q": "cute+kittenz"}` `?q=cute kittenz` or `?q=cute+kittenz` or `?q=cute+kittenz&mode=big` 119 | - `{"tags": ["cute", "kittenz"]}` `?tags=cute&tags=kittenz` 120 | - `{"q": undefined, inform: true}` `?q=&inform=true` 121 | 122 | ### params 123 | 124 | `{Object}` 125 | 126 | When the `express:` keyword is used in a string matcher, match only requests with these express parameters e.g `{"section": "feed", "user": "geoff"}` 127 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/Parameters/response.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # response 6 | 7 | Configures the http response returned by the mock. Accepts any of the following values or a `Promise` for any of them (useful when testing race conditions, loading transitions etc.). Unless otherwise stated, all responses have a `200` status 8 | 9 | ## Argument values 10 | 11 | ### Response 12 | 13 | `{Response}` 14 | A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response) instance to return unaltered. 15 | 16 | Note that it must use the same constructor as that used in the `fetch` implementation your application uses. [See how to configure this](/fetch-mock/docs/legacy-api/Usage/configuration#custom-fetch-implementations) e.g. `new Response('ok', {status: 200})` 17 | 18 | ### Status code 19 | 20 | `{Int}` 21 | Return a `Response` with the given status code. The response's `statusText` will also be set to the [default value corresponding to the status](https://fetch.spec.whatwg.org/#dom-response-statustext), e.g. `200` 22 | 23 | ### String 24 | 25 | `{String}` 26 | Return a 200 `Response` with the string as the response body e.g. `"Bad Response"` 27 | 28 | ### Response config 29 | 30 | `{Object}` 31 | 32 | If an object _only_ contains properties from among those listed below it is used to configure a `Response` to return. 33 | 34 | #### Object properties 35 | 36 | ##### body 37 | 38 | `{String|Object}` 39 | Set the `Response` body. See the non-config `Object` section of the docs below for behaviour when passed an `Object` e.g. `"Server responded ok"`, `{ token: 'abcdef' }` 40 | 41 | ##### status 42 | 43 | `{Int}` 44 | Sets the `Response` status e.g. `200` 45 | 46 | ##### headers 47 | 48 | `{Object}` 49 | Sets the `Response` headers, e.g `{'Content-Type': 'text/html'}` 50 | 51 | ##### redirectUrl 52 | 53 | `{String}` 54 | The url from which the `Response` should claim to originate from (to imitate followed directs). Will also set `redirected: true` on the response 55 | 56 | ##### throws 57 | 58 | `{Error}` 59 | Force `fetch` to return a `Promise` rejected with the value of `throws` e.g. `new TypeError('Failed to fetch')` 60 | 61 | ### Object 62 | 63 | `{Object|ArrayBuffer|...` 64 | If the `sendAsJson` option is set to `true`, any object that does not meet the criteria above will be converted to a `JSON` string and set as the response `body`. Otherwise, the object will be set as the response `body` (useful for `ArrayBuffer`s etc.) 65 | 66 | ### Promise 67 | 68 | `{Promise}` 69 | A `Promise` that resolves to any of the options documented above e.g. `new Promise(res => setTimeout(() => res(200), 50))` 70 | 71 | ### Function 72 | 73 | `{Function}` 74 | A function that returns any of the options documented above (including `Promise`. The function will be passed the `url` and `options` `fetch` was called with. If `fetch` was called with a `Request` instance, it will be passed `url` and `options` inferred from the `Request` instance, with the original `Request` passed as a third argument. 75 | 76 | #### Examples 77 | 78 | - `(url, opts) => opts.headers.Authorization ? 200 : 403` 79 | - `(_, _, request) => request.headers.get('Authorization') ? 200 : 403` 80 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 1, 3 | "collapsible": true, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/add-matcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # .addMatcher(options) 6 | 7 | Allows adding your own, reusable custom matchers to fetch-mock, for example a matcher for interacting with GraphQL queries, or an `isAuthorized` matcher that encapsulates the exact authorization conditions for the API you are mocking, and only requires a `true` or `false` to be input 8 | 9 | ## Options 10 | 11 | ### name 12 | 13 | `{String}` 14 | 15 | The name of your matcher. This will be the name of the property used to hold any input to your matcher. e.g. `graphqlVariables` 16 | 17 | ### usesBody 18 | 19 | `{Boolean}` 20 | 21 | If your matcher requires access to the body of the request set this to true; because body can, in some cases, only be accessed by fetch-mock asynchronously, you will need to provide this hint in order to make sure the correct code paths are followed. 22 | 23 | ### matcher 24 | 25 | `{Function}` 26 | 27 | A function which takes a route definition object as input, and returns a function of the signature `(url, options, request) => Boolean`. See the examples below for more detail. The function is passed the fetchMock instance as a second parameter in case you need to access any config. 28 | 29 | #### Examples 30 | 31 | ##### Authorization 32 | 33 | ```js 34 | fetchMock 35 | .addMatcher({ 36 | name: 'isAuthorized', 37 | matcher: 38 | ({ isAuthorized }) => 39 | (url, options) => { 40 | const actuallyIsAuthorized = options.headers && options.headers.auth; 41 | return isAuthorized ? actuallyIsAuthorized : !actuallyIsAuthorized; 42 | }, 43 | }) 44 | .mock({ isAuthorized: true }, 200) 45 | .mock({ isAuthorized: false }, 401); 46 | ``` 47 | 48 | ##### GraphQL 49 | 50 | ```js 51 | fetchMock 52 | .addMatcher({ 53 | name: 'graphqlVariables', 54 | matcher: ({graphqlVariables}) => (url, options) => { 55 | if (!/\/graphql$/.test(url)) { 56 | return false; 57 | } 58 | const body = JSON.parse(options.body) 59 | return body.variables && Object.keys(body.variables).length === Object.keys(body.graphqlVariables).length && Object.entries(graphqlVariables).every(([key, val]) => body.variables[key] === val) 60 | } 61 | }) 62 | .mock({graphqlVariables: {owner: 'wheresrhys'}}, {data: {account: { 63 | name: 'wheresrhys', 64 | repos: [ ... ] 65 | }}}) 66 | ``` 67 | 68 | One intent behind this functionality is to allow companies or publishers of particular toolsets to provide packages that extend fetch-mock to provide a more user friendly experience for developers using fetch to interact with their APIs. The GraphQL use case is a good example of this - the things which a developer might want to match on are buried in the request body, and written in a non-javascript query language. Please get in touch if you'd like to collaborate on writing such a package. 69 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/catch.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # .catch(response) 6 | 7 | Specifies how to respond to calls to `fetch` that don't match any mocks. 8 | 9 | It accepts any valid [fetch-mock response](/fetch-mock/docs/legacy-api/API/Mocking/Parameters/response), and can also take an arbitrary function to completely customise behaviour. If no argument is passed, then every unmatched call will receive a `200` response 10 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/mock.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # .mock(matcher, response, optionsOrName) 6 | 7 | Initialises or extends a stub implementation of fetch, applying a `route` that matches `matcher`, delivers a `Response` configured using `response`, and that respects the additional `options`. The stub will record its calls so they can be inspected later. If `.mock` is called on the top level `fetch-mock` instance, this stub function will also replace `fetch` globally. Calling `.mock()` with no arguments will carry out this stubbing without defining any mock responses. 8 | 9 | In the documentation, **route** is often used to refer to the combination of matching and responding behaviour set up using a single call to `mock()` 10 | 11 | ## Parameters 12 | 13 | ### matcher 14 | 15 | `{String|Regex|Function|Object}` 16 | 17 | Determines which calls to `fetch` should be handled by this route 18 | 19 | Alternatively a single parameter, `options`, an Object with `matcher`, `response` and other options defined, can be passed in. 20 | 21 | Note that if you use matchers that target anything other than the url string, you may also need to add a `name` to your matcher object so that a) you can add multiple mocks on the same url that differ only in other properties (e.g. query strings or headers) b) if you [inspect](/fetch-mock/docs/legacy-api/API/Inspection/inspecting-calls) the result of the fetch calls, retrieving the correct results will be easier. 22 | 23 | ### response 24 | 25 | `{String|Int|Object|Function|Promise|Response}` 26 | 27 | Response to send when a call is matched 28 | 29 | ### optionsOrName 30 | 31 | `{Object|String}` 32 | 33 | More options to configure matching and responding behaviour. Alternatively, use this parameter to pass a string to use as a name for the route in order to make using the call inspection API easier. 34 | 35 | ## Matching on multiple criteria 36 | 37 | For complex matching (e.g. matching on headers in addition to url), there are 4 patterns to choose from: 38 | 39 | 1. Use an object as the first argument, e.g. 40 | 41 | ```js 42 | fetchMock.mock({ url, headers }, response); 43 | ``` 44 | 45 | This has the advantage of keeping all the matching criteria in one place. 46 | 47 | 2. Pass in options in a third parameter e.g. 48 | 49 | ```js 50 | fetchMock.mock(url, response, { headers }); 51 | ``` 52 | 53 | This splits matching criteria between two parameters, which is arguably harder to read. However, if most of your tests only match on url, then this provides a convenient way to create a variant of an existing test. 54 | 55 | 3. Use a single object, e.g. 56 | 57 | ```js 58 | fetchMock.mock({ url, response, headers }); 59 | ``` 60 | 61 | Nothing wrong with doing this, but keeping response configuration in a separate argument to the matcher config feels like a good split. 62 | 63 | 4. Use a function matcher e.g. 64 | 65 | ```js 66 | fetchMock.mock((url, options) => { 67 | // write your own logic 68 | }, response); 69 | ``` 70 | 71 | Avoid using this unless you need to match on some criteria fetch-mock does not support. 72 | 73 | ## Examples 74 | 75 | ### Strings 76 | 77 | ```js 78 | fetchMock 79 | .mock('http://it.at.here/route', 200) 80 | .mock('begin:http://it', 200) 81 | .mock('end:here/route', 200) 82 | .mock('path:/route', 200) 83 | .mock('*', 200); 84 | ``` 85 | 86 | ### Complex Matchers 87 | 88 | ```js 89 | fetchMock 90 | .mock(/.*\.here.*/, 200) 91 | .mock((url, opts) => opts.method === 'patch', 200) 92 | .mock('express:/:type/:id', 200, { 93 | params: { 94 | type: 'shoe', 95 | }, 96 | }) 97 | .mock( 98 | { 99 | headers: { Authorization: 'Bearer 123' }, 100 | method: 'POST', 101 | }, 102 | 200, 103 | ); 104 | ``` 105 | 106 | ### Responses 107 | 108 | ```js 109 | fetchMock 110 | .mock('*', 'ok') 111 | .mock('*', 404) 112 | .mock('*', {results: []}) 113 | .mock('*', {throw: new Error('Bad kitty'))) 114 | .mock('*', new Promise(res => setTimeout(res, 1000, 404))) 115 | .mock('*', (url, opts) => { 116 | status: 302, 117 | headers: { 118 | Location: url.replace(/^http/, 'https') 119 | }, 120 | })) 121 | ``` 122 | 123 | ### End to end example 124 | 125 | ```js 126 | fetchMock.mock('begin:http://it.at.here/api', 403).mock( 127 | { 128 | url: 'begin:http://it.at.here/api', 129 | headers: { 130 | authorization: 'Basic dummy-token', 131 | }, 132 | }, 133 | 200, 134 | ); 135 | 136 | callApi('/endpoint', 'dummy-token').then((res) => { 137 | expect(res.status).to.equal(200); 138 | }); 139 | ``` 140 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/shorthands.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Shorthand methods 6 | 7 | These methods allow configuring routes for common use cases while avoiding writing configuration objects. Unless noted otherwise, each of the methods below have the same signature as `.mock(matcher, response, optionsOrName)` 8 | 9 | ## .once() 10 | 11 | Shorthand for `mock()` which creates a route that can only mock a single request. (see `repeat` option above) 12 | 13 | ## .any(response, options) 14 | 15 | Shorthand for `mock()` which creates a route that will return a response to any fetch request. 16 | 17 | ## .anyOnce(response, options) 18 | 19 | Creates a route that responds to any single request 20 | 21 | ## .get(), .post(), .put(), .delete(), .head(), .patch() 22 | 23 | Shorthands for `mock()` that create routes that only respond to requests using a particular http method. 24 | 25 | If you use some other method a lot you can easily define your own shorthands e.g. 26 | 27 | ```javascript 28 | fetchMock.purge = function (matcher, response, options) { 29 | return this.mock( 30 | matcher, 31 | response, 32 | Object.assign({}, options, { method: 'PURGE' }), 33 | ); 34 | }; 35 | ``` 36 | 37 | ## .getOnce(), .postOnce(), .putOnce(), .deleteOnce(), .headOnce(), .patchOnce() 38 | 39 | Creates a route that only responds to a single request using a particular http method 40 | 41 | ## .getAny(), .postAny(), .putAny(), .deleteAny(), .headAny(), .patchAny() 42 | 43 | Creates a route that responds to any requests using a particular http method. 44 | As with `.any()`, these only accept the parameters `(response, options)`. 45 | 46 | ## .getAnyOnce(), .postAnyOnce(), .putAnyOnce(), .deleteAnyOnce(), .headAnyOnce(), .patchAnyOnce() 47 | 48 | Creates a route that responds to any single request using a particular http method. 49 | As with `.any()`, these only accept the parameters `(response, options)`. 50 | 51 | ## .sticky() 52 | 53 | Shorthand for `mock()` which creates a route that persists even when `restore()`, `reset()` or `resetbehavior()` are called; 54 | 55 | This method is particularly useful for setting up fixtures that must remain in place for all tests, e.g. 56 | 57 | ```js 58 | fetchMock.sticky(/config-hub.com/, require('./fixtures/start-up-config.json')); 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/Mocking/spy.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # .spy(matcher) 6 | 7 | Records call history while passing each call on to `fetch` to be handled by the network. Optionally pass in a `matcher` to scope this to only matched calls, e.g. to fetch a specific resource from the network. 8 | 9 | To use `.spy()` on a sandboxed `fetchMock`, `fetchMock.config.fetch` must be set to the same `fetch` implementation used in your application. [See how to configure this](/fetch-mock/docs/legacy-api/Usage/configuration#custom-fetch-implementations). By default this will be the locally installed version of `node-fetch` 10 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/API/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 2, 3 | "collapsible": false, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Troubleshooting/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 3, 3 | "collapsible": true, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Troubleshooting/cookies.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Setting cookies in the browser 6 | 7 | The `Set-Cookie` header is used to set cookies in the browser. This behaviour is part of the [browser/http spec](https://tools.ietf.org/html/rfc6265#section-4.1), not the fetch spec. As fetch-mock prevents requests getting out of js and into the browser, `Set-Cookie` will have no effect. 8 | 9 | The following code samples demonstrate how to replicate the normal cookie setting behaviour when using fetch-mock. 10 | 11 | ## Set up 12 | 13 | ```js 14 | fetchMock.get('https://mydomain.com', () => { 15 | const cookieString = 'mycookie=hello; Max-Age=3600; Path=/;'; 16 | document.cookie = cookieString; 17 | return { status: 200, headers: { 'Set-Cookie': cookieString } }; 18 | }); 19 | ``` 20 | 21 | ## Tear down 22 | 23 | ```js 24 | fetchMock.reset(); 25 | document.cookie = 'mycookie=; Max-Age=0'; 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Troubleshooting/custom-classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Custom fetch implementation 6 | 7 | `fetch-mock` uses `Request`, `Response` and `Headers` constructors internally, and obtains these from `node-fetch` in Node.js, or `window` in the browser. If you are using an alternative implementation of `fetch` you will need to configure `fetch-mock` to use its implementations of these constructors instead. These should be set on the `fetchMock.config` object, e.g. 8 | 9 | ```javascript 10 | const ponyfill = require('fetch-ponyfill')(); 11 | Object.assign(fetchMock.config, { 12 | Headers: ponyfill.Headers, 13 | Request: ponyfill.Request, 14 | Response: ponyfill.Response, 15 | fetch: ponyfill, 16 | }); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Troubleshooting/debug-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Debugging 6 | 7 | The first step when debugging tests should be to run with the environment variable `DEBUG=fetch-mock*`. This will output additional logs for debugging purposes. 8 | 9 | Note that this was removed in version 10. 10 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Troubleshooting/global-non-global.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Global or non-global 6 | 7 | `fetch` can be used by your code globally or locally. It's important to determine which one applies to your codebase as it will impact how you use `fetch-mock` 8 | 9 | ## Global fetch 10 | 11 | In the following scenarios `fetch` will be a global 12 | 13 | - When using native `fetch` (or a polyfill) in the browser 14 | - When `node-fetch` has been assigned to `global` in your Node.js process (a pattern sometimes used in isomorphic codebases) 15 | 16 | By default fetch-mock assumes `fetch` is a global so no more setup is required once you've required `fetch-mock`. 17 | 18 | ## Non-global fetch library 19 | 20 | In the following scenarios `fetch` will not be a global 21 | 22 | - Using [node-fetch](https://www.npmjs.com/package/node-fetch) in Node.js without assigning to `global` 23 | - Using [fetch-ponyfill](https://www.npmjs.com/package/fetch-ponyfill) in the browser 24 | - Using libraries which use fetch-ponyfill internally 25 | - Some build setups result in a non-global `fetch`, though it may not always be obvious that this is the case 26 | 27 | The `sandbox()` method returns a function that can be used as a drop-in replacement for `fetch`. Pass this into your mocking library of choice. The function returned by `sandbox()` has all the methods of `fetch-mock` exposed on it, e.g. 28 | 29 | ```js 30 | const fetchMock = require('fetch-mock'); 31 | const myMock = fetchMock.sandbox().mock('/home', 200); 32 | // pass myMock in to your application code, instead of fetch, run it, then... 33 | expect(myMock.called('/home')).to.be.true; 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Troubleshooting/importing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Importing the correct version 6 | 7 | _Note that the documentation below applies to version 9 and below._ 8 | 9 | The JS ecosystem is in a transitional period between module systems, and there are also a number of different build tools available, all with their own idosyncratic opinions about how JS should be compiled. The following detail may help debug any problems, and a few known workarounds are listed below. 10 | 11 | ## Built files 12 | 13 | In general `server` refers to the version of the source code designed for running in nodejs, whereas `client` refers to the version designed to run in the browser. As well as this distinction, fetch-mock builds several versions of itself: 14 | 15 | - `/cjs` directory - this contains a copy of the source files (which are currently written as commonjs modules). They are copied here in order to prevent direct requires from `/src`, which could make migrating the src to ES modules troublesome. `client.js` and `server.js` are the entry points. The directory also contains a `package.json` file specifying that the directory contains commonjs modules. 16 | - `/esm` directory - This contains builds of fetch-mock, exported as ES modules. `client.js` and `server.js` are the entry points. The bundling tool used is [rollup](https://rollupjs.org). 17 | - `/es5` directory - This contains builds of fetch-mock which do not use any JS syntax not included in the [ES5 standard](https://es5.github.io/), i.e. excludes recent additions to the language. It contains 4 entry points: 18 | - `client.js` and `server.js`, both of which are commonjs modules 19 | - `client-legacy.js`, which is the same as `client.js`, but includes some babel polyfill bootstrapping to ease running it in older environments 20 | - `client-bundle.js`, `client-legacy-bundle.js`, which are standalone [UMD](https://github.com/umdjs/umd) bundles of the es5 client code that can be included in the browser using an ordinary script tag. The bundling tool used is [rollup](https://rollupjs.org). 21 | 22 | ## Importing the right file 23 | 24 | The package.json file references a selection of the above built files: 25 | 26 | ```json 27 | { 28 | "main": "./cjs/server.js", 29 | "browser": "./esm/client.js", 30 | "module": "./esm/server.js" 31 | } 32 | ``` 33 | 34 | These are intended to target the most common use cases at the moment: 35 | 36 | - nodejs using commonjs 37 | - nodejs using ES modules 38 | - bundling tools such as webpack 39 | 40 | In most cases, your environment & tooling will use the config in package.json to import the correct file when you `import` or `require` `fetch-mock` by its name only. 41 | 42 | However, `import`/`require` will sometimes get it wrong. Below are a few scenarios where you may need to directly reference a different entry point. 43 | 44 | - If your client-side code or tests do not use a loader that respects the `browser` field of `package.json` use `require('fetch-mock/es5/client')` or `import fetchMock from 'fetch-mock/esm/client'`. 45 | - When not using any bundler in the browser, use one of the following as the src of a script tag: `node_modules/fetch-mock/es5/client-bundle.js`, `node_modules/fetch-mock/es5/client-legacy-bundle.js`. This loads fetch-mock into the `fetchMock` global variable. 46 | - For Node.js 6 or lower use `require('fetch-mock/es5/server')` 47 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Troubleshooting/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Troubleshooting 6 | 7 | The first step when debugging tests should be to run with the environment variable `DEBUG=fetch-mock*`. This will output additional logs for debugging purposes. 8 | 9 | ### `fetch` is assigned to a local variable, not a global 10 | 11 | First of all, consider whether you could just use `fetch` as a global. Here are 3 reasons why this is a good idea: 12 | 13 | - The `fetch` standard defines it as a global (and in some cases it won't work unless bound to `window`), so to write isomorphic code it's probably best to stick to this pattern 14 | - [`isomorphic-fetch`](https://www.npmjs.com/package/isomorphic-fetch) takes care of installing it as a global in Node.js or the browser, so there's no effort on your part to do so. 15 | - `fetch-mock` is primarily designed to work with `fetch` as a global and your experience of using it will be far more straightforward if you follow this pattern 16 | 17 | Still not convinced? 18 | 19 | In that case `fetchMock.sandbox()` can be used to generate a function which you can pass in to a mock loading library such as [`mockery`](https://www.npmjs.com/package/mockery) instead of `fetch` 20 | 21 | ### `fetch` doesn't seem to be getting mocked? 22 | 23 | - If using a mock loading library such as `mockery`, are you requiring the module you're testing after registering `fetch-mock` with the mock loader? You probably should be ([Example incorrect usage](https://github.com/wheresrhys/fetch-mock/issues/70)). If you're using ES6 `import` it may not be possible to do this without reverting to using `require()` sometimes. 24 | - If using `isomorphic-fetch` in your source, are you assigning it to a `fetch` variable? You _shouldn't_ be i.e. 25 | - `import 'isomorphic-fetch'`, not `import fetch from 'isomorphic-fetch'` 26 | - `require('isomorphic-fetch')`, not `const fetch = require('isomorphic-fetch')` 27 | 28 | ### Environment doesn't support requiring fetch-mock? 29 | 30 | - If your client-side code or tests do not use a loader that respects the browser field of package.json use `require('fetch-mock/es5/client')`. 31 | - If you need to use fetch-mock without commonjs, you can include the precompiled `node_modules/fetch-mock/es5/client-browserified.js` in a script tag. This loads fetch-mock into the `fetchMock` global variable. 32 | - For server side tests running in Node.js 0.12 or lower use `require('fetch-mock/es5/server')` 33 | 34 | ### Matching `Request` objects in node fails 35 | 36 | In node, if your `Request` object is not an instance of the `Request` 37 | constructor used by fetch-mock, you need to set a reference to your custom 38 | request class. This needs to be done if you are mocking the `Request` object 39 | for a test or you are running npm with a version below 3. 40 | 41 | - use `fetchMock.config.Request = myRequest`, where `myRequest` is a reference to the Request constructor used in your application code. 42 | 43 | it.md 44 | 45 | - When using karma-webpack it's best not to use the `webpack.ProvidePlugin` for this. Instead just add `node_modules/whatwg-fetch/fetch.js` to your list of files to include, or require it directly into your tests before requiring fetch-mock. 46 | 47 | - chaining 48 | 49 | - note that end matches qs, path matches only path 50 | 51 | Put this with the spy() docs 52 | When using `node-fetch`, `fetch-mock` will use the instance you have installed. The one exception is that a reference to `fetchMock.config.fetch = require('node-fetch')` is required if you intend to use the `.spy()` method) 53 | 54 | to Within individual tests `.catch()` and `spy()` can be used for fine-grained control of this" 55 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Usage/Usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | fetch-mock is the most comprehensive library for mocking the fetch API. 5 | 6 | It allows mocking http requests made using [fetch](https://fetch.spec.whatwg.org/) or a library imitating its api, such as [node-fetch](https://www.npmjs.com/package/node-fetch). 7 | 8 | It supports most JavaScript environments, including browsers, Node.js, web workers and service workers. 9 | 10 | As well as shorthand methods for the simplest use cases, it offers a flexible API for customising all aspects of mocking behaviour. 11 | 12 | ## Example 13 | 14 | ```js 15 | fetchMock.mock('http://example.com', 200); 16 | const res = await fetch('http://example.com'); 17 | assert(res.ok); 18 | fetchMock.restore(); 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Usage/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 1, 3 | "collapsible": false, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Usage/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Configuration 6 | 7 | On any `fetch-mock` instance, set configuration options directly on the `fetchMock.config` object. e.g. 8 | 9 | ```js 10 | const fetchMock = require('fetch-mock'); 11 | fetchMock.config.sendAsJson = false; 12 | ``` 13 | 14 | ## Available options 15 | 16 | Options marked with a `†` can also be overridden for individual calls to `.mock(matcher, response, options)` by setting as properties on the `options` parameter 17 | 18 | ### sendAsJson 19 | 20 | `{Boolean}` default: `true` 21 | 22 | Always convert objects passed to `.mock()` to JSON strings before building reponses. Can be useful to set to `false` globally if e.g. dealing with a lot of `ArrayBuffer`s. When `true` the `Content-Type: application/json` header will also be set on each response. 23 | 24 | ### includeContentLength 25 | 26 | `{Boolean}` default: `true` 27 | 28 | Sets a `Content-Length` header on each response. 29 | 30 | ### fallbackToNetwork 31 | 32 | `{Boolean|String}` default: `false` 33 | 34 | - `true`: Unhandled calls fall through to the network 35 | - `false`: Unhandled calls throw an error 36 | - `'always'`: All calls fall through to the network, effectively disabling fetch-mock. 37 | 38 | ### overwriteRoutes 39 | 40 | `{Boolean}` default: `undefined` 41 | 42 | Configures behaviour when attempting to add a new route with the same name (or inferred name) as an existing one 43 | 44 | - `undefined`: An error will be thrown 45 | - `true`: Overwrites the existing route 46 | - `false`: Appends the new route to the list of routes 47 | 48 | ### matchPartialBody 49 | 50 | `{Boolean}` default: `false` 51 | 52 | Match calls that only partially match a specified body json. Uses the [is-subset](https://www.npmjs.com/package/is-subset) library under the hood, which implements behaviour the same as jest's [.objectContaining()](https://jestjs.io/docs/en/expect#expectobjectcontainingobject) method. 53 | 54 | ### warnOnFallback 55 | 56 | `{Boolean}` default: `true` 57 | 58 | Print a warning if any call is caught by a fallback handler (set using `catch()`, `spy()` or the `fallbackToNetwork` option) 59 | 60 | ### Custom fetch implementations 61 | 62 | `fetch`, `Headers`, `Request`, `Response` can all be set on the configuration object, allowing fetch-mock to mock any implementation if you are not using the default one for the environment. 63 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Usage/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Installation 6 | 7 | Install fetch-mock using 8 | 9 | ```bash 10 | npm install --save-dev fetch-mock 11 | ``` 12 | 13 | fetch-mock supports both [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) and [commonjs](https://requirejs.org/docs/commonjs.html). The following should work in most environments. Check the [importing the correct version](/fetch-mock/docs/legacy-api/Troubleshooting/importing) section of the docs if you experience problems. 14 | 15 | ## ES modules 16 | 17 | ```js 18 | import fetchMock from 'fetch-mock'; 19 | ``` 20 | 21 | ## Commonjs 22 | 23 | ```js 24 | const fetchMock = require('fetch-mock'); 25 | ``` 26 | 27 | ## Using with Jest 28 | 29 | Please try out the new jest-friendly wrapper for fetch-mock, [fetch-mock-jest](https://github.com/wheresrhys/fetch-mock-jest). 30 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Usage/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Quickstart 6 | 7 | ## Setting up your mock 8 | 9 | - The commonest use case is `fetchMock.mock(matcher, response)`, where `matcher` is an exact url or regex to match, and `response` is a status code, string or object literal. 10 | - You can also use `fetchMock.once()` to limit to a single call or `fetchMock.get()`, `fetchMock.post()` etc. to limit to a method. 11 | - All these methods are chainable so you can easily define several mocks in a single test. 12 | 13 | ```javascript 14 | fetchMock 15 | .get('http://good.com/', 200) 16 | .post('http://good.com/', 400) 17 | .get(/bad\.com/, 500); 18 | ``` 19 | 20 | ## Analysing calls to your mock 21 | 22 | - `fetchMock.called(matcher)` reports if any calls matched your mock (or leave `matcher` out if you just want to check `fetch` was called at all). 23 | - `fetchMock.lastCall()`, `fetchMock.lastUrl()` or `fetchMock.lastOptions()` give you access to the parameters last passed in to `fetch`. 24 | - `fetchMock.done()` will tell you if `fetch` was called the expected number of times. 25 | 26 | ## Tearing down your mock 27 | 28 | - `fetchMock.resetHistory()` resets the call history. 29 | - `fetchMock.reset()` or `fetchMock.restore()` will also restore `fetch()` to its native implementation 30 | 31 | ## Example 32 | 33 | Example with Node.js: suppose we have a file `make-request.js` with a function that calls `fetch`: 34 | 35 | ```js 36 | module.exports = function makeRequest() { 37 | return fetch('http://httpbin.org/my-url', { 38 | headers: { 39 | user: 'me', 40 | }, 41 | }).then(function (response) { 42 | return response.json(); 43 | }); 44 | }; 45 | ``` 46 | 47 | We can use fetch-mock to mock `fetch`. In `mocked.js`: 48 | 49 | ```js 50 | var makeRequest = require('./make-request'); 51 | var fetchMock = require('fetch-mock'); 52 | 53 | // Mock the fetch() global to return a response 54 | fetchMock.get( 55 | 'http://httpbin.org/my-url', 56 | { hello: 'world' }, 57 | { 58 | delay: 1000, // fake a slow network 59 | headers: { 60 | user: 'me', // only match requests with certain headers 61 | }, 62 | }, 63 | ); 64 | 65 | makeRequest().then(function (data) { 66 | console.log('got data', data); 67 | }); 68 | 69 | // Unmock. 70 | fetchMock.reset(); 71 | ``` 72 | 73 | Result: 74 | 75 | ```bash 76 | $ node mocked.js 77 | 'got data' { hello: 'world' } 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Usage/requirements.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0.5 3 | --- 4 | 5 | # Requirements 6 | 7 | fetch-mock requires the following to run: 8 | 9 | - [Node.js](https://Node.js.org/) 8+ for full feature operation 10 | - [Node.js](https://Node.js.org/) 0.12+ with [limitations](http://www.wheresrhys.co.uk/fetch-mock/installation) 11 | - [npm](https://www.npmjs.com/package/npm) (normally comes with Node.js) 12 | - Either 13 | - [node-fetch](https://www.npmjs.com/package/node-fetch) when testing in Node.js. To allow users a choice over which version to use, `node-fetch` is not included as a dependency of `fetch-mock`. 14 | - A browser that supports the `fetch` API either natively or via a [polyfill/ponyfill](https://ponyfoo.com/articles/polyfills-or-ponyfills) 15 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/Usage/versions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Versions 6 | 7 | ## Version 10/11 8 | 9 | These have 2 major differences from previous versions 10 | 11 | 1. It is written using ES modules 12 | 2. It uses native fetch in node.js 13 | 14 | Version 11 is identical to version 10, with the exception that it changes the commonjs to use `exports.default = fetchMock` instead of `exports=fetchMock`. 15 | 16 | If you experience any compatibility issues upgrading from version 9, please either 17 | 18 | - try the approaches iom the troubleshooting section of these docs 19 | - downgrade to v9 again 20 | 21 | I intend to keep version 10 and above reasonably clean, with as few workarounds for different toolchains as possible. Hopefully, as other toolchains gradually migrate to ESM and native fetch then fetch-mock will eventually be compatible with their latest versions. 22 | 23 | ### Differences 24 | 25 | #### Not set as a global in the browser 26 | 27 | Previously the browser buiodl of fetch-mock set `window.fetchMock`. This is no longer the case. If your testing toolchain requires it's available as a global you may import a `fetch-mock-wrapper.js` file, defined as follows, rather than `fetch-mock` directly: 28 | 29 | ```js 30 | import fetchMock from 'fetch-mock'; 31 | window.fetchMock = fetchMock; 32 | ``` 33 | 34 | ## Version 9 and below 35 | 36 | v7, v8 & v9 are practically identical, only differing in their treatment of a few edge cases, or in compatibility with other libraries and environments. 37 | 38 | For previous versions follow the documentation below: 39 | 40 | - [v7 upgrade guide](https://github.com/wheresrhys/fetch-mock/blob/master/docs/v6-v7-upgrade-guide.md) 41 | - [v6 docs](https://github.com/wheresrhys/fetch-mock/tree/4231044aa94e234b53e296181ca5b6b4cecb6e3f/docs) 42 | - [v5 docs](https://github.com/wheresrhys/fetch-mock/tree/b8270640d5711feffb01d1bf85bb7da95179c4de/docs) 43 | -------------------------------------------------------------------------------- /docs/docs/legacy-api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: Legacy API 3 | sidebar_position: 800 4 | --- 5 | 6 | # Legacy API docs 7 | 8 | This documentation is for version 11 and below of fetch-mock 9 | -------------------------------------------------------------------------------- /docs/docs/wrappers/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: Wrappers 3 | sidebar_position: 8 4 | --- 5 | 6 | - [@fetch-mock/jest](/fetch-mock/docs/wrappers/jest) 7 | - [@fetch-mock/vitest](/fetch-mock/docs/wrappers/vitest) 8 | -------------------------------------------------------------------------------- /docs/docs/wrappers/jest.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: '@fetch-mock/jest' 3 | sidebar_position: 1 4 | --- 5 | 6 | # @fetch-mock/jest 7 | 8 | A wrapper for [fetch-mock](/fetch-mock/docs/) that improves the developer experience when working with jest. It provides the following: 9 | 10 | - Adds methods to fetchMock which wrap its default methods, but align more closely with jest's naming conventions. 11 | - Extends `expect` with convenience methods allowing for expressive tests such as `expect(fetchMock).toHavePosted('http://example.com', {id: 'test-id'})`. 12 | - Can optionally be hooked in to jest's global mock management methods such as `clearAllMocks()`. 13 | 14 | ## Requirements 15 | 16 | @fetch-mock/jest requires either of the following to run: 17 | 18 | - [jest](https://jest.dev/guide/) 19 | - The `fetch` API, via one of the following: 20 | - [Node.js](https://nodejs.org/) 18+ for full feature operation 21 | - Any modern browser that supports the `fetch` API 22 | - [node-fetch](https://www.npmjs.com/package/node-fetch) when testing in earlier versions of Node.js (this is untested, but should mostly work) 23 | 24 | ## Installation 25 | 26 | ```shell 27 | npm i -D @fetch-mock/jest 28 | ``` 29 | 30 | ## Setup 31 | 32 | ```js 33 | import fetchMock, { manageFetchMockGlobally } from '@fetch-mock/jest'; 34 | import { jest } from '@jest/globals'; 35 | 36 | manageFetchMockGlobally(jest); // optional 37 | ``` 38 | 39 | ### JSDOM campatibility 40 | 41 | To use with JSDOM, import the following into your test environment instead of using `jest-environment-jsdom` directly: 42 | 43 | ```js 44 | import { TestEnvironment } from 'jest-environment-jsdom'; 45 | 46 | export default class CustomTestEnvironment extends TestEnvironment { 47 | async setup() { 48 | await super.setup(); 49 | 50 | this.global.Request = Request; 51 | this.global.Response = Response; 52 | this.global.ReadableStream = ReadableStream; 53 | } 54 | } 55 | ``` 56 | 57 | ## API 58 | 59 | ### fetchMock (default export) 60 | 61 | An instance of [fetch-mock](/fetch-mock/docs/), with the following methods added: 62 | 63 | #### fetchMock.mockClear() 64 | 65 | Clears all call history from the mocked `fetch` implementation. 66 | 67 | #### fetchMock.mockReset() 68 | 69 | `fetchMock.mockReset({includeSticky: boolean})` 70 | 71 | Clears all call history from the mocked `fetch` implementation _and_ removes all routes (including fallback routes defined using `.spy()` or `.catch()`) with the exception of sticky routes. To remove these, pass in the `includeSticky: true` option. FOr more fine grained control over fallback routes and named routes please use `fetchMock.removeRoutes()` 72 | 73 | #### fetchMock.mockRestore() 74 | 75 | `fetchMock.mockRestore({includeSticky: boolean})` 76 | 77 | Calls `mockReset()` and additionally restores global fetch to its unmocked implementation. 78 | 79 | ### manageFetchMockGlobally(jest) 80 | 81 | Hooks fetchMock up to jest's global mock management so that 82 | 83 | - `jest.clearAllMocks()` will call `fetchMock.mockClear()` 84 | - `jest.resetAllMocks()` will call `fetchMock.mockReset()` 85 | - `jest.restoreAllMocks()` will call `fetchMock.mockRestore()` 86 | 87 | Note that these **will not** clear any sticky routes added to fetchMock. You will need to make an additional call to `fetchMock.removeRoutes({includeSticky: true})`. 88 | 89 | ### Expect extensions 90 | 91 | These are added to jest automatically and are available on any expect call that is passed `fetchMock` (or `fetch`, if it has been mocked globally by fetchMock) as an argument. Their behaviour is similar to the jest expectation methods mentioned in the comments below 92 | 93 | ```js 94 | expect(fetchMock).toHaveFetched(filter, options); // .toHaveBeenCalled()/.toHaveBeenCalledWith() 95 | expect(fetchMock).toHaveLastFetched(filter, options); // .toHaveBeenLastCalledWith() 96 | expect(fetchMock).toHaveNthFetched(n, filter, options); // .toHaveBeenNthCalled()/.toHaveBeenNthCalledWith() 97 | expect(fetchMock).toHaveFetchedTimes(n, filter, options); // .toHaveBeenCalledTimes() 98 | expect(fetchMock).toBeDone(filter); 99 | ``` 100 | 101 | ### Notes 102 | 103 | - `filter` and `options` are the same as those used by `fetchMock.callHistory.calls()`. 104 | - Each method can be prefixed with the `.not` helper for negative assertions. e.g. `expect(fetchMock).not.toBeDone('my-route')` 105 | - In each of the method names `Fetched` can be replaced by any of the following verbs to scope to a particular method: 106 | - Got 107 | - Posted 108 | - Put 109 | - Deleted 110 | - FetchedHead 111 | - Patched 112 | e.g. `expect(fetchMock).toHaveDeleted('http://example.com/user/1')` 113 | -------------------------------------------------------------------------------- /docs/docs/wrappers/vitest.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: '@fetch-mock/vitest' 3 | sidebar_position: 1 4 | --- 5 | 6 | # @fetch-mock/vitest 7 | 8 | A wrapper for [fetch-mock](/fetch-mock/docs/) that improves the developer experience when working with vitest. It provides the following: 9 | 10 | - Adds methods to fetchMock which wrap its default methods, but align more closely with vitest's naming conventions. 11 | - Extends `expect` with convenience methods allowing for expressive tests such as `expect(fetchMock).toHavePosted('http://example.com', {id: 'test-id'})`. 12 | - Can optionally be hooked in to vitest's global mock management methods such as `clearAllMocks()`. 13 | 14 | ## Requirements 15 | 16 | @fetch-mock/vitest requires either of the following to run: 17 | 18 | - [vitest](https://vitest.dev/guide/) 19 | - The `fetch` API, via one of the following: 20 | - [Node.js](https://nodejs.org/) 18+ for full feature operation 21 | - Any modern browser that supports the `fetch` API 22 | - [node-fetch](https://www.npmjs.com/package/node-fetch) when testing in earlier versions of Node.js (this is untested, but should mostly work) 23 | 24 | ## Installation 25 | 26 | ```shell 27 | npm i -D @fetch-mock/vitest 28 | ``` 29 | 30 | ## Setup 31 | 32 | ```js 33 | import fetchMock, { manageFetchMockGlobally } from '@fetch-mock/vitest'; 34 | 35 | manageFetchMockGlobally(); // optional 36 | ``` 37 | 38 | ## API 39 | 40 | ### fetchMock (default export) 41 | 42 | An instance of [fetch-mock](/fetch-mock/docs/), with the following methods added: 43 | 44 | #### fetchMock.mockClear() 45 | 46 | Clears all call history from the mocked `fetch` implementation. 47 | 48 | #### fetchMock.mockReset() 49 | 50 | `fetchMock.mockReset({includeSticky: boolean})` 51 | 52 | Clears all call history from the mocked `fetch` implementation _and_ removes all routes (including fallback routes defined using `.spy()` or `.catch()`) with the exception of sticky routes. To remove these, pass in the `includeSticky: true` option. For more fine grained control over fallback routes and named routes please use `fetchMock.removeRoutes()` 53 | 54 | #### fetchMock.mockRestore() 55 | 56 | `fetchMock.mockRestore({includeSticky: boolean})` 57 | 58 | Calls `mockReset()` and additionally restores global fetch to its unmocked implementation. 59 | 60 | ### manageFetchMockGlobally() 61 | 62 | Hooks fetchMock up to vitest's global mock management so that 63 | 64 | - `vi.clearAllMocks()` will call `fetchMock.mockClear()` 65 | - `vi.resetAllMocks()` will call `fetchMock.mockReset()` 66 | - `vi.restoreAllMocks()` will call `fetchMock.mockRestore()` 67 | - `vi.unstubAllGlobals()` will also call `fetchMock.mockRestore()` 68 | 69 | Note that these **will not** clear any sticky routes added to fetchMock. You will need to make an additional call to `fetchMock.removeRoutes({includeSticky: true})`. 70 | 71 | ### Expect extensions 72 | 73 | These are added to vitest automatically and are available on any expect call that is passed fetchMock as an argument. Their behaviour is similar to the vitest expectation methods mentioned in the comments below 74 | 75 | ```js 76 | expect(fetchMock).toHaveFetched(filter, options); // .toHaveBeenCalled()/.toHaveBeenCalledWith() 77 | expect(fetchMock).toHaveLastFetched(filter, options); // .toHaveBeenLastCalledWith() 78 | expect(fetchMock).toHaveNthFetched(n, filter, options); // .toHaveBeenNthCalled()/.toHaveBeenNthCalledWith() 79 | expect(fetchMock).toHaveFetchedTimes(n, filter, options); // .toHaveBeenCalledTimes() 80 | expect(fetchMock).toBeDone(filter); 81 | ``` 82 | 83 | ### Notes 84 | 85 | - `filter` and `options` are the same as those used by `fetchMock.callHistory.calls()`. 86 | - Each method can be prefixed with the `.not` helper for negative assertions. e.g. `expect(fetchMock).not.toBeDone('my-route')` 87 | - In each of the method names `Fetched` can be replaced by any of the following verbs to scope to a particular method: 88 | - Got 89 | - Posted 90 | - Put 91 | - Deleted 92 | - FetchedHead 93 | - Patched 94 | e.g. `expect(fetchMock).toHaveDeleted('http://example.com/user/1')` 95 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // `@type` JSDoc annotations allow editor autocompletion and type checking 3 | // (when paired with `@ts-check`). 4 | // There are various equivalent ways to declare your Docusaurus config. 5 | // See: https://docusaurus.io/docs/api/docusaurus-config 6 | 7 | import { themes as prismThemes } from 'prism-react-renderer'; 8 | 9 | /** @type {import('@docusaurus/types').Config} */ 10 | const config = { 11 | title: 'Fetch Mock', 12 | tagline: 'Powerful mocking of the fetch API.', 13 | favicon: 'img/favicon.ico', 14 | projectName: 'wheresrhys.github.io', 15 | organizationName: 'wheresrhys', 16 | // Set the production url of your site here 17 | url: 'https://www.wheresrhys.co.uk', 18 | // Set the // pathname under which your site is served 19 | // For GitHub pages deployment, it is often '//' 20 | baseUrl: '/fetch-mock', 21 | 22 | onBrokenLinks: 'throw', 23 | onBrokenMarkdownLinks: 'warn', 24 | 25 | // Even if you don't use internationalization, you can use this field to set 26 | // useful metadata like html lang. For example, if your site is Chinese, you 27 | // may want to replace "en" with "zh-Hans". 28 | i18n: { 29 | defaultLocale: 'en', 30 | locales: ['en'], 31 | }, 32 | 33 | presets: [ 34 | [ 35 | 'classic', 36 | /** @type {import('@docusaurus/preset-classic').Options} */ 37 | ({ 38 | docs: { 39 | sidebarPath: './sidebars.js', 40 | // Please change this to your repo. 41 | // Remove this to remove the "edit this page" links. 42 | editUrl: 'https://github.com/wheresrhys/fetch-mock/edit/main/docs', 43 | }, 44 | theme: { 45 | customCss: './src/css/custom.css', 46 | }, 47 | }), 48 | ], 49 | ], 50 | 51 | themeConfig: 52 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 53 | ({ 54 | // Replace with your project's social card 55 | image: 'img/docusaurus-social-card.jpg', 56 | navbar: { 57 | title: 'Fetch Mock', 58 | items: [ 59 | { 60 | type: 'docSidebar', 61 | sidebarId: 'fetchMockSidebar', 62 | position: 'left', 63 | label: 'Docs' 64 | }, 65 | { 66 | href: '/fetch-mock/docs/legacy-api', 67 | label: 'Legacy docs', 68 | }, 69 | { to: 'blog', label: 'Blog', position: 'right' }, 70 | { 71 | href: 'https://github.com/wheresrhys/fetch-mock', 72 | label: 'GitHub', 73 | position: 'right', 74 | }, 75 | ], 76 | }, 77 | prism: { 78 | theme: prismThemes.github, 79 | darkTheme: prismThemes.dracula, 80 | }, 81 | }), 82 | }; 83 | 84 | export default config; 85 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^3.6.1", 18 | "@docusaurus/preset-classic": "^3.6.1", 19 | "@mdx-js/react": "^3.0.0", 20 | "clsx": "^2.0.0", 21 | "prism-react-renderer": "^2.3.0", 22 | "react": "^18.0.0", 23 | "react-dom": "^18.0.0" 24 | }, 25 | "devDependencies": { 26 | "@docusaurus/module-type-aliases": "^3.6.1", 27 | "@docusaurus/types": "^3.6.1" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.5%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 3 chrome version", 37 | "last 3 firefox version", 38 | "last 5 safari version" 39 | ] 40 | }, 41 | "engines": { 42 | "node": ">=18.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | fetchMockSidebar: [{ type: 'autogenerated', dirName: '.' }] 18 | 19 | }; 20 | 21 | export default sidebars; 22 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Heading from '@theme/Heading'; 3 | import styles from './styles.module.css'; 4 | 5 | const FeatureList = [ 6 | { 7 | title: 'Easy to Use', 8 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 9 | description: ( 10 | <> 11 | An intuitive, powerful API for creating a mock that targets different 12 | requests based on a variety of criteria. 13 | 14 | ), 15 | }, 16 | { 17 | title: 'Comprehensive', 18 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 19 | description: ( 20 | <> 21 | Allows the behaviour of most aspects of the fetch API to be overridden. 22 | 23 | ), 24 | }, 25 | { 26 | title: 'Flexible', 27 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 28 | description: ( 29 | <> 30 | Runs in a wide range of environments, from node.js to service workers, 31 | and compatible with most testing frameworks. 32 | 33 | ), 34 | }, 35 | ]; 36 | 37 | function Feature({ Svg, title, description }) { 38 | return ( 39 |
40 |
41 | 42 |
43 |
44 | {title} 45 |

{description}

46 |
47 |
48 | ); 49 | } 50 | 51 | export default function HomepageFeatures() { 52 | return ( 53 |
54 |
55 |
56 | {FeatureList.map((props, idx) => ( 57 | 58 | ))} 59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | 32 | 33 | a.navbar__link.new::after { 34 | content: '(experimental)'; 35 | display: inline-block; 36 | padding-left: 3px; 37 | color: green; 38 | vertical-align: super; 39 | font-size: 0.8em; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /docs/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Link from '@docusaurus/Link'; 3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 4 | import Layout from '@theme/Layout'; 5 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 6 | 7 | import Heading from '@theme/Heading'; 8 | import styles from './index.module.css'; 9 | 10 | function HomepageHeader() { 11 | const { siteConfig } = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 | 16 | {siteConfig.title} 17 | 18 |

{siteConfig.tagline}

19 |
20 | 24 | Get started 25 | 26 |
27 |
28 |
29 | ); 30 | } 31 | 32 | export default function Home() { 33 | const { siteConfig } = useDocusaurusContext(); 34 | return ( 35 | 39 | 40 |
41 | 42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wheresrhys/fetch-mock/c9358bcf07d7f367d1bc1a527aaf4044f160b492/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wheresrhys/fetch-mock/c9358bcf07d7f367d1bc1a527aaf4044f160b492/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wheresrhys/fetch-mock/c9358bcf07d7f367d1bc1a527aaf4044f160b492/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wheresrhys/fetch-mock/c9358bcf07d7f367d1bc1a527aaf4044f160b492/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from '@eslint/js'; 4 | import tseslint from 'typescript-eslint'; 5 | import globals from 'globals'; 6 | import eslintConfigPrettier from 'eslint-config-prettier'; 7 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 8 | 9 | export default [ 10 | { 11 | ignores: [ 12 | 'docs/**/*.js', 13 | 'packages/*/test/fixtures/*', 14 | 'packages/**/__tests__/fixtures/*', 15 | '**/dist/**/*', 16 | 'packages/fetch-mock/types/index.test-d.ts', 17 | ], 18 | }, 19 | eslint.configs.recommended, 20 | ...tseslint.configs.recommended.map((config) => { 21 | return { ...config, ignores: ['packages/codemods/**'] }; 22 | }), 23 | eslintConfigPrettier, 24 | { 25 | rules: { 26 | 'no-prototype-builtins': 0, 27 | '@typescript-eslint/no-wrapper-object-types': 0, 28 | }, 29 | languageOptions: { 30 | globals: { 31 | ...globals.node, 32 | }, 33 | }, 34 | }, 35 | { 36 | files: [ 37 | 'import-compat/*', 38 | '**/*.cjs', 39 | 'packages/fetch-mock/test/fixtures/fetch-proxy.js', 40 | ], 41 | rules: { 42 | '@typescript-eslint/no-require-imports': 0, 43 | }, 44 | }, 45 | { 46 | files: ['packages/fetch-mock/test/**/*.js'], 47 | languageOptions: { 48 | globals: { 49 | testGlobals: 'writable', 50 | }, 51 | }, 52 | }, 53 | { 54 | files: ['packages/fetch-mock/test/fixtures/sw.js'], 55 | languageOptions: { 56 | globals: { 57 | ...globals.browser, 58 | }, 59 | }, 60 | }, 61 | eslintPluginPrettierRecommended, 62 | ]; 63 | -------------------------------------------------------------------------------- /import-compat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fetch-mock-compat-tests", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "compat:mts:esm": "tsc ts-esm.mts --target esnext --moduleResolution nodenext --module nodenext && node ts-esm.mjs || echo \"\\033[0;31mfailed mts:esm\\033[0m\"", 6 | "compat:ts:esm": "tsc ts-esm.ts --target esnext --moduleResolution nodenext --module nodenext && node ts-esm.js || echo \"\\033[0;31mfailed ts:esm\\033[0m\"", 7 | "compat:ts:cjs": "tsc ts-cjs.ts --target esnext --moduleResolution nodenext --module nodenext && node ts-cjs.js || echo \"\\033[0;31mfailed ts:cjs\\033[0m\"", 8 | "compat:js:esm": "cp ts-esm.ts js-esm.mjs && node js-esm.mjs || echo \"\\033[0;31mfailed js:esm\\033[0m\"", 9 | "compat:js:cjs": "cp ts-cjs.ts js-cjs.js && node js-cjs.js || echo \"\\033[0;31mfailed js:cjs\\033[0m\"", 10 | "compat:module": "npm run compat:mts:esm && npm run compat:ts:esm && npm run compat:ts:cjs && npm run compat:js:esm && npm run compat:js:cjs" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /import-compat/ts-cjs.ts: -------------------------------------------------------------------------------- 1 | const { default: fetchMockCore, FetchMock } = require('fetch-mock'); 2 | fetchMockCore.route('http://example.com', 200); 3 | 4 | new FetchMock({}); 5 | -------------------------------------------------------------------------------- /import-compat/ts-esm.mts: -------------------------------------------------------------------------------- 1 | import fetchMockCore, { FetchMock } from 'fetch-mock'; 2 | fetchMockCore.route('http://example.com', 200); 3 | 4 | new FetchMock({}); 5 | -------------------------------------------------------------------------------- /import-compat/ts-esm.ts: -------------------------------------------------------------------------------- 1 | import fetchMockCore, { FetchMock } from 'fetch-mock'; 2 | fetchMockCore.route('http://example.com', 200); 3 | 4 | new FetchMock({}); 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} **/ 2 | export default { 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+.tsx?$': ['ts-jest', {}], 6 | }, 7 | moduleNameMapper: { 8 | '@fetch-mock/core': '/packages/core/dist/cjs/index.js', 9 | '(.+)\\.js': '$1', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "rootDir": "src", 4 | "compilerOptions": { 5 | "module": "NodeNext", 6 | "noEmit": true, 7 | "target": "es2021" 8 | }, 9 | "include": ["./packages/**/src/*.ts"], 10 | "exclude": ["node_modules", "packages/**/__tests__"] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fetch-mock-monorepo", 3 | "description": "Mock http requests made using fetch (or isomorphic-fetch)", 4 | "version": "1.0.0", 5 | "type": "module", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/wheresrhys/fetch-mock.git" 9 | }, 10 | "author": "Rhys Evans", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/wheresrhys/fetch-mock/issues" 14 | }, 15 | "homepage": "http://www.wheresrhys.co.uk/fetch-mock", 16 | "engines": { 17 | "node": ">=8.0.0" 18 | }, 19 | "workspaces": [ 20 | "packages/*", 21 | "docs", 22 | "import-compat" 23 | ], 24 | "scripts": { 25 | "lint:staged": "eslint --cache --fix", 26 | "lint": "eslint --cache --fix .", 27 | "lint:ci": "eslint .", 28 | "prettier": "prettier --cache --write *.md \"./**/*.md\" *.json \"./**/*.json\"", 29 | "prettier:ci": "prettier *.md \"./**/*.md\" *.json \"./**/*.json\"", 30 | "types:check": "tsc --project ./jsconfig.json && echo 'types check done'", 31 | "prepare": "husky || echo \"husky not available\"", 32 | "build": "npm run build -w=packages", 33 | "docs": "npm run start -w docs", 34 | "test": "vitest . --ui --exclude=packages/jest/src/__tests__/*", 35 | "test:ci": "vitest . --typecheck --reporter=junit --outputFile=test-results/junit.xml --coverage.provider=istanbul --exclude=packages/jest/src/__tests__/*", 36 | "test:jest": "node --experimental-vm-modules node_modules/jest/bin/jest.js packages/jest/src/__tests__/*.spec.?s", 37 | "test:browser": "vitest . --browser.enabled --browser.name chromium --browser.provider playwright --exclude=packages/{jest,codemods}/src/__tests__/*", 38 | "coverage:send": "cat ./coverage/lcov.info | coveralls", 39 | "compat:module": "npm run compat:module -w import-compat" 40 | }, 41 | "devDependencies": { 42 | "@commitlint/cli": "^19.3.0", 43 | "@commitlint/config-conventional": "^19.2.2", 44 | "@eslint/js": "^9.8.0", 45 | "@rollup/plugin-commonjs": "^26.0.1", 46 | "@rollup/plugin-node-resolve": "^15.2.3", 47 | "@testing-library/dom": "^10.4.0", 48 | "@types/chai": "^4.3.17", 49 | "@types/eslint__js": "^8.42.3", 50 | "@types/events": "^3.0.3", 51 | "@types/node": "^20.14.10", 52 | "@vitest/browser": "^3.0.0", 53 | "@vitest/coverage-istanbul": "^3.0.0", 54 | "@vitest/coverage-v8": "^3.0.0", 55 | "@vitest/ui": "^3.0.0", 56 | "eslint": "9.14.0", 57 | "eslint-config-prettier": "^9.1.0", 58 | "eslint-plugin-prettier": "^5.2.1", 59 | "expect-type": "^1.1.0", 60 | "globals": "^15.9.0", 61 | "husky": "^9.0.11", 62 | "jest": "^29.7.0", 63 | "jest-environment-jsdom": "^29.7.0", 64 | "jsdom": "^23.2.0", 65 | "lint-staged": "^15.2.7", 66 | "playwright": "^1.46.0", 67 | "prettier": "^3.1.1", 68 | "rollup": "^4.22.4", 69 | "rollup-plugin-copy": "^3.5.0", 70 | "rollup-plugin-sourcemaps": "^0.6.3", 71 | "ts-jest": "^29.2.5", 72 | "tsd": "^0.31.1", 73 | "typescript": "^5.5.4", 74 | "typescript-eslint": "^8.0.1", 75 | "v8": "^0.1.0", 76 | "vitest": "^3.0.5", 77 | "webdriverio": "^9.12.2" 78 | }, 79 | "volta": { 80 | "node": "20.18.0" 81 | }, 82 | "lint-staged": { 83 | "**/*.js": [ 84 | "npm run lint:staged" 85 | ], 86 | "packages/**/*.ts": [ 87 | "npm run types:check" 88 | ], 89 | "**/*.md": [ 90 | "npm run prettier" 91 | ] 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /packages/codemods/.npmignore: -------------------------------------------------------------------------------- 1 | try.js 2 | -------------------------------------------------------------------------------- /packages/codemods/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.1.3](https://github.com/wheresrhys/fetch-mock/compare/codemods-v0.1.2...codemods-v0.1.3) (2025-02-04) 4 | 5 | 6 | ### Features 7 | 8 | * added overwriteRoutes: true rule to codemods ([b3d1468](https://github.com/wheresrhys/fetch-mock/commit/b3d1468f93fb1bf18b5d3bf8c0a21dd56ad4d0aa)) 9 | 10 | 11 | ### Documentation Changes 12 | 13 | * added documentation fro new modifyRoute method ([0d007d6](https://github.com/wheresrhys/fetch-mock/commit/0d007d6d3acba0ea23b64a08ef03806ba5c827f0)) 14 | 15 | ## [0.1.2](https://github.com/wheresrhys/fetch-mock/compare/codemods-v0.1.1...codemods-v0.1.2) (2024-11-15) 16 | 17 | 18 | ### Features 19 | 20 | * update codemods to use hardReset() ([757d480](https://github.com/wheresrhys/fetch-mock/commit/757d480532cfa8054471dec1bfcd89688966e37b)) 21 | 22 | ## [0.1.1](https://github.com/wheresrhys/fetch-mock/compare/codemods-v0.1.0...codemods-v0.1.1) (2024-09-25) 23 | 24 | 25 | ### Documentation Changes 26 | 27 | * fix mistakes in codemods docs ([315365e](https://github.com/wheresrhys/fetch-mock/commit/315365e86b7e15d5bc725cd61dd8b1893f7c5fad)) 28 | 29 | ## [0.1.0](https://github.com/wheresrhys/fetch-mock/compare/codemods-v0.1.0...codemods-v0.1.0) (2024-09-25) 30 | 31 | 32 | ### Documentation Changes 33 | 34 | * fix mistakes in codemods docs ([315365e](https://github.com/wheresrhys/fetch-mock/commit/315365e86b7e15d5bc725cd61dd8b1893f7c5fad)) 35 | 36 | ## 0.1.0 (2024-09-19) 37 | 38 | 39 | ### Features 40 | 41 | * add an error to warn of calls() signature change ([5bf6f67](https://github.com/wheresrhys/fetch-mock/commit/5bf6f6765fe54a2eb0e1cc9424df02b721b9610c)) 42 | * add an error to warn of lastCall() signature change ([62fc48c](https://github.com/wheresrhys/fetch-mock/commit/62fc48c8cce70267fe044088ca6cafab4139766c)) 43 | * allow manually specifying extra variables that hold fetch-mock instances ([317ede7](https://github.com/wheresrhys/fetch-mock/commit/317ede7ea305fd9df9c498444df2b7a0e1350449)) 44 | * can identify fetch mock in esm ([e523711](https://github.com/wheresrhys/fetch-mock/commit/e523711b5c05e2adeea96a4478c681c603b329b4)) 45 | * codemod fro fallbackToNetwork option ([c272f65](https://github.com/wheresrhys/fetch-mock/commit/c272f65e903e6e835edbf3a087def0aded796b30)) 46 | * codemod reset() ([a96f62b](https://github.com/wheresrhys/fetch-mock/commit/a96f62b60e95e691dd0f783de419db01a1b92302)) 47 | * codemod resetHistory to clearHistory ([5f1203e](https://github.com/wheresrhys/fetch-mock/commit/5f1203ebfcc173e3293c61e24c7348c5810f1a2e)) 48 | * codemod restore() ([21e54af](https://github.com/wheresrhys/fetch-mock/commit/21e54afbd6eeb47aff1cff4f9ac367b89da4198f)) 49 | * converted codemods to use tsx parser for greater reuse ([376d5c3](https://github.com/wheresrhys/fetch-mock/commit/376d5c3de38a74f38b320f8b3dacffec44993861)) 50 | * finished implementing reset method codemods ([e9d59df](https://github.com/wheresrhys/fetch-mock/commit/e9d59dff405625289eb1378a7943d1cde1950125)) 51 | * first codemod for editing an option implemented ([6ec5575](https://github.com/wheresrhys/fetch-mock/commit/6ec55750ce0eeb1a79ce3559c7d25dbaf9919650)) 52 | * lastOptions() and lastResponse() ([300e0b8](https://github.com/wheresrhys/fetch-mock/commit/300e0b83e0bd5be40c975ace46af86166b7c75e1)) 53 | * lastUrl() codemod implemented ([a115a27](https://github.com/wheresrhys/fetch-mock/commit/a115a2733defc8dbca0554abfaa25750daa9f8fe)) 54 | * midway through implementing codemods for sandbox ([dbb0b43](https://github.com/wheresrhys/fetch-mock/commit/dbb0b431b8d1894d032236199ae206f7790dbb2a)) 55 | * more option codemods ([23201b0](https://github.com/wheresrhys/fetch-mock/commit/23201b0f3ff34f04ae80152847d35d228550ca31)) 56 | * progress on codemod to alter global options ([f790205](https://github.com/wheresrhys/fetch-mock/commit/f7902051c992c869f34481959fef8e59468a7f6d)) 57 | * remiving options from methods more or less works ([e976e7e](https://github.com/wheresrhys/fetch-mock/commit/e976e7edc87927d66570ca41c1a10187d22566e5)) 58 | * remove codemods for sandbox() method ([9a06c1e](https://github.com/wheresrhys/fetch-mock/commit/9a06c1e4386b8d4c33f6b7fea50be49634308fb4)) 59 | * set up basic codemods package structure ([a76cee9](https://github.com/wheresrhys/fetch-mock/commit/a76cee9887d4e2bd56cdb0564ca787190f028aff)) 60 | * support converting getAny() etc ([1ef70ec](https://github.com/wheresrhys/fetch-mock/commit/1ef70ec9bfc7364712f900655bd6f194e2c45b0a)) 61 | * use get("*") as a better replacement for getAny() ([3b7daf7](https://github.com/wheresrhys/fetch-mock/commit/3b7daf71663662265c63169ba036b13e0856b053)) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * avoided converting jest.mock() to jest.route() ([a42817b](https://github.com/wheresrhys/fetch-mock/commit/a42817b392cc035653f0be149a6d51b0dcc53de4)) 67 | * changed to use j.ObjectProperty instead of j.Property ([78c2e35](https://github.com/wheresrhys/fetch-mock/commit/78c2e3541e0be25d267ea47c28ee53641634ce4a)) 68 | * defined types for codemod ([0c3debf](https://github.com/wheresrhys/fetch-mock/commit/0c3debf039d052a92de590cce3ca9d772a76a880)) 69 | * reinstated codemods for options on all methods ([3a610b7](https://github.com/wheresrhys/fetch-mock/commit/3a610b7fce666cb2954715d28c2732ccbabcb960)) 70 | 71 | 72 | ### Documentation Changes 73 | 74 | * document how to use transform with jodeshift ([517a6ac](https://github.com/wheresrhys/fetch-mock/commit/517a6ac297e1d5c5bad194a771fa4e1419bd10ad)) 75 | * start documenting usage of codemods ([f4a3c39](https://github.com/wheresrhys/fetch-mock/commit/f4a3c39de4540c7338d65ca59636b413bef11964)) 76 | -------------------------------------------------------------------------------- /packages/codemods/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fetch-mock/codemods", 3 | "description": "Codemods for upgrading fetch-mock", 4 | "version": "0.1.3", 5 | "main": "./src/index.js", 6 | "type": "commonjs", 7 | "engines": { 8 | "node": ">=18.11.0" 9 | }, 10 | "repository": { 11 | "directory": "packages/codemods", 12 | "type": "git", 13 | "url": "git+https://github.com/wheresrhys/fetch-mock.git" 14 | }, 15 | "scripts": { 16 | "build": "echo 'no build'" 17 | }, 18 | "license": "MIT", 19 | "author": "Rhys Evans", 20 | "bugs": { 21 | "url": "https://github.com/wheresrhys/fetch-mock/issues" 22 | }, 23 | "homepage": "http://www.wheresrhys.co.uk/fetch-mock", 24 | "dependencies": { 25 | "jscodeshift": "^17.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/codemods/src/__tests__/fixtures/extra-vars.js: -------------------------------------------------------------------------------- 1 | const fetchMock = require('fetch-mock'); 2 | fetchMock.mock('blah', 200); 3 | fm1.mock('blah', 200); 4 | fm2.mock('blah', 200); 5 | -------------------------------------------------------------------------------- /packages/codemods/src/__tests__/fixtures/jsx.jsx: -------------------------------------------------------------------------------- 1 | import fetchMock from 'fetch-mock'; 2 | fetchMock.mock("blah",
Content
); 3 | -------------------------------------------------------------------------------- /packages/codemods/src/__tests__/fixtures/tsx.tsx: -------------------------------------------------------------------------------- 1 | import fetchMock from 'fetch-mock'; 2 | function helper (res: number): void { 3 | fetchMock.mock("blah",
Content
); 4 | }; 5 | -------------------------------------------------------------------------------- /packages/codemods/src/__tests__/fixtures/typescript.ts: -------------------------------------------------------------------------------- 1 | import fetchMock from 'fetch-mock'; 2 | function helper (res: number): void { 3 | fetchMock.mock("blah", res); 4 | }; 5 | -------------------------------------------------------------------------------- /packages/codemods/src/__tests__/identifying-fetchmock-instances.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { codemod } from '../index'; 3 | import jscodeshift from 'jscodeshift'; 4 | 5 | function expectCodemodResult(src, expected) { 6 | expect(codemod(src, jscodeshift)).toEqual(expected); 7 | } 8 | 9 | describe('identifying fetch-mock instances', () => { 10 | it('cjs require() named fetchMock', () => { 11 | expectCodemodResult( 12 | ` 13 | const fetchMock = require('fetch-mock'); 14 | fetchMock.mock("blah", 200) 15 | `, 16 | ` 17 | const fetchMock = require('fetch-mock'); 18 | fetchMock.route("blah", 200) 19 | `, 20 | ); 21 | }); 22 | it('cjs require() named something else', () => { 23 | expectCodemodResult( 24 | ` 25 | const fetchNot = require('fetch-mock'); 26 | fetchNot.mock("blah", 200) 27 | `, 28 | ` 29 | const fetchNot = require('fetch-mock'); 30 | fetchNot.route("blah", 200) 31 | `, 32 | ); 33 | }); 34 | it('esm import named fetchMock', () => { 35 | expectCodemodResult( 36 | ` 37 | import fetchMock from 'fetch-mock'; 38 | fetchMock.mock("blah", 200) 39 | `, 40 | ` 41 | import fetchMock from 'fetch-mock'; 42 | fetchMock.route("blah", 200) 43 | `, 44 | ); 45 | }); 46 | it('esm import named something else', () => { 47 | expectCodemodResult( 48 | ` 49 | import fetchNot from 'fetch-mock'; 50 | fetchNot.mock("blah", 200) 51 | `, 52 | ` 53 | import fetchNot from 'fetch-mock'; 54 | fetchNot.route("blah", 200) 55 | `, 56 | ); 57 | }); 58 | it.skip('unassigned instances of require("fetch-mock")', () => { 59 | expectCodemodResult( 60 | `require('fetch-mock').mock("blah", 200)`, 61 | `require('fetch-mock').route("blah", 200)`, 62 | ); 63 | }); 64 | it.skip('sandbox() instances', () => { 65 | expectCodemodResult( 66 | ` 67 | const fetchMock = require('fetch-mock'); 68 | const fm = fetchMock.sandbox(); 69 | fm.mock("blah", 200) 70 | `, 71 | ` 72 | const fetchMock = require('fetch-mock'); 73 | const fm = fetchMock.sandbox(); 74 | fm.route("blah", 200) 75 | `, 76 | ); 77 | }); 78 | it.skip('sandbox() instances used by jest / vitest.mock', () => {}); 79 | it.skip('identify multiple instances on a page e.g. fetchMock and fm=fetchMock.sandbox()', () => {}); 80 | it.skip('identify when a fm instance with lots of chained methods is assigned to a new variable', () => { 81 | expectCodemodResult( 82 | ` 83 | const fetchMock = require('fetch-mock'); 84 | const fm = fetchMock.get('a', 'b').get('a', 'b'); 85 | fm.mock("blah", 200) 86 | `, 87 | ` 88 | const fetchMock = require('fetch-mock'); 89 | const fm = fetchMock.get('a', 'b').get('a', 'b'); 90 | fm.route("blah", 200) 91 | `, 92 | ); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /packages/codemods/src/__tests__/integration.test.js: -------------------------------------------------------------------------------- 1 | import { it, describe, expect, vi } from 'vitest'; 2 | import { promisify } from 'node:util'; 3 | import { exec as callbackExec } from 'node:child_process'; 4 | const exec = promisify(callbackExec); 5 | vi.setConfig({ 6 | testTimeout: 10_000, 7 | }); 8 | 9 | describe('integration', () => { 10 | it('can operate on typescript', async () => { 11 | const { stdout } = await exec( 12 | 'npx jscodeshift --parser ts -p -d -t ./packages/codemods/src/index.js ./packages/codemods/src/__tests__/fixtures/typescript.ts', 13 | ); 14 | expect(stdout).toContain(`import fetchMock from 'fetch-mock'; 15 | function helper (res: number): void { 16 | fetchMock.route("blah", res); 17 | }`); 18 | }); 19 | it('can operate on jsx', async () => { 20 | const { stdout } = await exec( 21 | 'npx jscodeshift --parser ts -p -d -t ./packages/codemods/src/index.js ./packages/codemods/src/__tests__/fixtures/jsx.jsx', 22 | ); 23 | expect(stdout).toContain(`import fetchMock from 'fetch-mock'; 24 | fetchMock.route("blah",
Content
);`); 25 | }); 26 | 27 | it('can operate on tsx', async () => { 28 | const { stdout } = await exec( 29 | 'npx jscodeshift --parser ts -p -d -t ./packages/codemods/src/index.js ./packages/codemods/src/__tests__/fixtures/tsx.tsx', 30 | ); 31 | expect(stdout).toContain(`import fetchMock from 'fetch-mock'; 32 | function helper (res: number): void { 33 | fetchMock.route("blah",
Content
); 34 | }`); 35 | }); 36 | it('allow passing in one or more additional variable names for fetch-mock', async () => { 37 | const { stdout } = await exec( 38 | 'FM_VARIABLES=fm1,fm2 npx jscodeshift --parser ts -p -d -t ./packages/codemods/src/index.js ./packages/codemods/src/__tests__/fixtures/extra-vars.js', 39 | ); 40 | expect(stdout).toContain(`const fetchMock = require('fetch-mock'); 41 | fetchMock.route('blah', 200); 42 | fm1.route('blah', 200); 43 | fm2.route('blah', 200);`); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/codemods/src/__tests__/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /packages/codemods/src/codemods/methods.js: -------------------------------------------------------------------------------- 1 | const j = require('jscodeshift').withParser('tsx'); 2 | 3 | function getAllChainedMethodCalls(fetchMockVariableName, root) { 4 | return root 5 | .find(j.CallExpression, { 6 | callee: { 7 | object: { 8 | type: 'Identifier', 9 | name: fetchMockVariableName, 10 | }, 11 | }, 12 | }) 13 | .map((path) => { 14 | const paths = [path]; 15 | while ( 16 | !['ArrowFunctionExpression', 'ExpressionStatement'].includes( 17 | path.parentPath.value.type, 18 | ) 19 | ) { 20 | path = path.parentPath; 21 | if (path.value.type === 'CallExpression') { 22 | paths.push(path); 23 | } 24 | } 25 | return paths; 26 | }); 27 | } 28 | 29 | module.exports.getAllChainedMethodCalls = getAllChainedMethodCalls; 30 | module.exports.simpleMethods = function (fetchMockVariableName, root) { 31 | const fetchMockMethodCalls = getAllChainedMethodCalls( 32 | fetchMockVariableName, 33 | root, 34 | ); 35 | 36 | fetchMockMethodCalls.forEach((path) => { 37 | const method = path.value.callee.property.name; 38 | if (method === 'mock') { 39 | path.value.callee.property.name = 'route'; 40 | } 41 | 42 | if (method === 'resetHistory') { 43 | path.value.callee.property.name = 'clearHistory'; 44 | } 45 | 46 | ['get', 'post', 'put', 'delete', 'head', 'patch'].some((httpMethod) => { 47 | let prependStar = false; 48 | if (method === `${httpMethod}Any`) { 49 | prependStar = true; 50 | path.value.callee.property.name = httpMethod; 51 | } else if (method === `${httpMethod}AnyOnce`) { 52 | prependStar = true; 53 | path.value.callee.property.name = `${httpMethod}Once`; 54 | } 55 | if (prependStar) { 56 | path.value.arguments.unshift(j.stringLiteral('*')); 57 | } 58 | }); 59 | }); 60 | ['reset', 'restore', 'resetBehavior'].forEach((methodName) => { 61 | root 62 | .find(j.CallExpression, { 63 | callee: { 64 | object: { 65 | type: 'Identifier', 66 | name: fetchMockVariableName, 67 | }, 68 | property: { 69 | name: methodName, 70 | }, 71 | }, 72 | }) 73 | .forEach((path) => { 74 | const sticky = path?.value?.arguments[0]?.properties?.find( 75 | (prop) => prop.key.name === 'sticky', 76 | )?.value?.value; 77 | let expressionsString; 78 | if (methodName === 'resetBehavior') { 79 | expressionsString = ` 80 | fetchMock.removeRoutes(${sticky ? '{includeSticky: true}' : ''}); 81 | fetchMock.unmockGlobal();`; 82 | } else { 83 | expressionsString = ` 84 | fetchMock.hardReset(${sticky ? '{includeSticky: true}' : ''});`; 85 | } 86 | const newExpressions = j(expressionsString) 87 | .find(j.ExpressionStatement) 88 | .paths(); 89 | const insertLocation = j(path) 90 | .closest(j.ExpressionStatement) 91 | .replaceWith(newExpressions.shift().value); 92 | while (newExpressions.length) { 93 | insertLocation.insertAfter(newExpressions.pop().value); 94 | } 95 | }); 96 | }); 97 | 98 | [ 99 | ['lastUrl', 'url'], 100 | ['lastOptions', 'options'], 101 | ['lastResponse', 'response'], 102 | ].forEach(([oldMethod, newProperty]) => { 103 | root 104 | .find(j.CallExpression, { 105 | callee: { 106 | object: { 107 | type: 'Identifier', 108 | name: fetchMockVariableName, 109 | }, 110 | property: { 111 | name: oldMethod, 112 | }, 113 | }, 114 | }) 115 | .closest(j.ExpressionStatement) 116 | .replaceWith((path) => { 117 | const oldCall = j(path).find(j.CallExpression).get(); 118 | const builder = j( 119 | `${fetchMockVariableName}.callHistory.lastCall()?.${newProperty}`, 120 | ); 121 | const newCall = builder.find(j.CallExpression).get(); 122 | newCall.value.arguments = oldCall.value.arguments; 123 | return builder.find(j.ExpressionStatement).get().value; 124 | }); 125 | }); 126 | 127 | root 128 | .find(j.CallExpression, { 129 | callee: { 130 | object: { 131 | type: 'Identifier', 132 | name: fetchMockVariableName, 133 | }, 134 | property: { 135 | name: 'lastCall', 136 | }, 137 | }, 138 | }) 139 | .closest(j.ExpressionStatement) 140 | .insertBefore( 141 | j( 142 | 'throw new Error("lastCall() now returns a CallLog object instead of an array. Refer to the documentation")', 143 | ) 144 | .find(j.ThrowStatement) 145 | .get().value, 146 | ); 147 | 148 | root 149 | .find(j.CallExpression, { 150 | callee: { 151 | object: { 152 | type: 'Identifier', 153 | name: fetchMockVariableName, 154 | }, 155 | property: { 156 | name: 'calls', 157 | }, 158 | }, 159 | }) 160 | .closest(j.ExpressionStatement) 161 | .insertBefore( 162 | j( 163 | 'throw new Error("calls() now returns an array of CallLog objects instead of an array of arrays. Refer to the documentation")', 164 | ) 165 | .find(j.ThrowStatement) 166 | .get().value, 167 | ); 168 | }; 169 | -------------------------------------------------------------------------------- /packages/codemods/src/codemods/options.js: -------------------------------------------------------------------------------- 1 | const j = require('jscodeshift').withParser('tsx'); 2 | const { getAllChainedMethodCalls } = require('./methods.js'); 3 | const simpleOptionNames = ['overwriteRoutes', 'warnOnFallback', 'sendAsJson']; 4 | 5 | function appendError(message, path) { 6 | path 7 | .closest(j.ExpressionStatement) 8 | .insertAfter( 9 | j(`throw new Error("${message}")`).find(j.ThrowStatement).get().value, 10 | ); 11 | } 12 | module.exports.simpleOptions = function (fetchMockVariableName, root) { 13 | const configSets = root 14 | .find(j.CallExpression, { 15 | callee: { 16 | object: { 17 | type: 'Identifier', 18 | name: 'Object', 19 | }, 20 | property: { name: 'assign' }, 21 | }, 22 | }) 23 | .filter((path) => { 24 | const firstArg = path.value.arguments[0]; 25 | const secondArg = path.value.arguments[1]; 26 | return ( 27 | firstArg.type === 'MemberExpression' && 28 | firstArg.property.name === 'config' && 29 | firstArg.object.type === 'Identifier' && 30 | firstArg.object.name === fetchMockVariableName && 31 | secondArg.type === 'ObjectExpression' 32 | ); 33 | }); 34 | [...simpleOptionNames, 'fallbackToNetwork'].forEach((name) => { 35 | const propertyAssignments = root.find(j.AssignmentExpression, { 36 | left: { 37 | type: 'MemberExpression', 38 | property: { name }, 39 | object: { 40 | type: 'MemberExpression', 41 | property: { name: 'config' }, 42 | object: { 43 | type: 'Identifier', 44 | name: fetchMockVariableName, 45 | }, 46 | }, 47 | }, 48 | }); 49 | const objectAssignments = configSets.find(j.ObjectProperty, { 50 | key: { name }, 51 | }); 52 | 53 | if (name === 'fallbackToNetwork') { 54 | const errorMessage = 55 | 'fallbackToNetwork option is deprecated. Use the `spyGlobal()` method instead'; 56 | appendError(errorMessage, propertyAssignments); 57 | appendError(errorMessage, objectAssignments); 58 | } 59 | propertyAssignments.remove(); 60 | objectAssignments.remove(); 61 | }); 62 | 63 | configSets 64 | .filter((path) => { 65 | const secondArg = path.value.arguments[1]; 66 | return secondArg.properties.length === 0; 67 | }) 68 | .remove(); 69 | 70 | const fetchMockMethodCalls = getAllChainedMethodCalls( 71 | fetchMockVariableName, 72 | root, 73 | j, 74 | ); 75 | 76 | [ 77 | 'once', 78 | 'route', 79 | 'sticky', 80 | 'any', 81 | 'anyOnce', 82 | 'get', 83 | 'getOnce', 84 | 'post', 85 | 'postOnce', 86 | 'put', 87 | 'putOnce', 88 | 'delete', 89 | 'deleteOnce', 90 | 'head', 91 | 'headOnce', 92 | 'patch', 93 | 'patchOnce', 94 | ].some((methodName) => { 95 | const optionsObjects = fetchMockMethodCalls 96 | .filter((path) => path.value.callee.property.name === methodName) 97 | .map((path) => { 98 | return j(path) 99 | .find(j.ObjectExpression) 100 | .filter((path) => { 101 | return path.value.properties.some(({ key }) => 102 | simpleOptionNames.includes(key.name), 103 | ); 104 | }) 105 | .paths(); 106 | }); 107 | if (!optionsObjects.length) { 108 | return; 109 | } 110 | simpleOptionNames.forEach((optionName) => { 111 | const properties = optionsObjects.find(j.ObjectProperty, { 112 | key: { name: optionName }, 113 | }); 114 | 115 | properties.forEach((path) => { 116 | if ( 117 | path.value.key.name === 'overwriteRoutes' && 118 | path.value.value.value === true 119 | ) { 120 | const errorMessage = 121 | '`overwriteRoutes: true` option is deprecated. Use the `modifyRoute()` method instead'; 122 | appendError(errorMessage, j(path)); 123 | } 124 | }); 125 | 126 | properties.remove(); 127 | }); 128 | optionsObjects 129 | .filter((path) => { 130 | return path.value.properties.length === 0; 131 | }) 132 | .remove(); 133 | }); 134 | }; 135 | -------------------------------------------------------------------------------- /packages/codemods/src/index.js: -------------------------------------------------------------------------------- 1 | const j = require('jscodeshift').withParser('tsx'); 2 | const { simpleOptions } = require('./codemods/options.js'); 3 | const { simpleMethods } = require('./codemods/methods.js'); 4 | 5 | function findFetchMockVariableName(root) { 6 | let fetchMockVariableName; 7 | try { 8 | fetchMockVariableName = root 9 | .find(j.CallExpression, { 10 | callee: { 11 | name: 'require', 12 | }, 13 | arguments: [{ value: 'fetch-mock' }], 14 | }) 15 | .closest(j.VariableDeclarator) 16 | .get().value.id.name; 17 | } catch { 18 | try { 19 | fetchMockVariableName = root 20 | .find(j.ImportDeclaration, { 21 | source: { value: 'fetch-mock' }, 22 | }) 23 | .find(j.ImportDefaultSpecifier) 24 | .get().value.local.name; 25 | } catch (err) { 26 | throw new Error('No fetch-mock references found', err); 27 | } 28 | } 29 | return fetchMockVariableName; 30 | } 31 | 32 | function codemod(source, variableName) { 33 | const root = j(source); 34 | const fetchMockVariableName = variableName || findFetchMockVariableName(root); 35 | simpleMethods(fetchMockVariableName, root); 36 | // run after simpleMethods because means the options rewriters have to iterate 37 | // over smaller list of methods 38 | simpleOptions(fetchMockVariableName, root); 39 | 40 | return root.toSource(); 41 | } 42 | 43 | function transformer(file) { 44 | let modifiedSource = codemod(file.source); 45 | if (process.env.FM_VARIABLES) { 46 | const extraVariables = process.env.FM_VARIABLES.split(','); 47 | extraVariables.forEach((variableName) => { 48 | modifiedSource = codemod(modifiedSource, variableName); 49 | }); 50 | } 51 | return modifiedSource; 52 | } 53 | 54 | module.exports = transformer; 55 | module.exports.codemod = codemod; 56 | -------------------------------------------------------------------------------- /packages/codemods/try.js: -------------------------------------------------------------------------------- 1 | const { codemod } = require('./src/index.js'); 2 | 3 | console.log( 4 | codemod( 5 | `const fetchMock = require('fetch-mock'); 6 | fetchMock.get('*', 200, {overwriteRoutes: true}); 7 | `, 8 | ), 9 | ); 10 | -------------------------------------------------------------------------------- /packages/fetch-mock/.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.* 2 | scripts 3 | src 4 | -------------------------------------------------------------------------------- /packages/fetch-mock/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rhys Evans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/fetch-mock/README.md: -------------------------------------------------------------------------------- 1 | # fetch-mock 2 | 3 | Features include: 4 | 5 | - mocks most of the fetch API spec, even advanced behaviours such as streaming and aborting 6 | - declarative matching for most aspects of a http request, including url, headers, body and query parameters 7 | - shorthands for the most commonly used features, such as matching a http method or matching one fetch only 8 | - support for delaying responses, or using your own async functions to define custom race conditions 9 | - can be used as a spy to observe real network requests 10 | - can be extended with your own reusable custom matchers that can be used both for matching fetch-calls and inspecting the results 11 | - isomorphic, and supports either a global fetch instance or a locally required instance 12 | 13 | ## Requirements 14 | 15 | @fetch-mock requires either of the following to run: 16 | 17 | - [Node.js](https://nodejs.org/) 18+ for full feature operation 18 | - Any modern browser that supports the `fetch` API 19 | - [node-fetch](https://www.npmjs.com/package/node-fetch) when testing in earlier versions of Node.js (this is untested, but should mostly work) 20 | 21 | ## Documentation and Usage 22 | 23 | See the [project website](https://www.wheresrhys.co.uk/fetch-mock/) 24 | 25 | ## License 26 | 27 | fetch-mock is licensed under the [MIT](https://github.com/wheresrhys/fetch-mock/blob/master/LICENSE) license. 28 | Copyright © 2024, Rhys Evans 29 | -------------------------------------------------------------------------------- /packages/fetch-mock/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fetch-mock", 3 | "description": "Mock http requests made using fetch", 4 | "version": "12.5.2", 5 | "exports": { 6 | "browser": "./dist/esm/index.js", 7 | "import": { 8 | "types": "./dist/esm/index.d.ts", 9 | "default": "./dist/esm/index.js" 10 | }, 11 | "require": { 12 | "types": "./dist/cjs/index.d.ts", 13 | "default": "./dist/cjs/index.js" 14 | } 15 | }, 16 | "main": "./dist/cjs/index.js", 17 | "module": "./dist/esm/index.js", 18 | "types": "./dist/esm/index.d.ts", 19 | "type": "module", 20 | "engines": { 21 | "node": ">=18.11.0" 22 | }, 23 | "dependencies": { 24 | "@types/glob-to-regexp": "^0.4.4", 25 | "dequal": "^2.0.3", 26 | "glob-to-regexp": "^0.4.1", 27 | "regexparam": "^3.0.0" 28 | }, 29 | "repository": { 30 | "directory": "packages/fetch-mock", 31 | "type": "git", 32 | "url": "git+https://github.com/wheresrhys/fetch-mock.git" 33 | }, 34 | "scripts": { 35 | "build": "rm -rf dist && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node ../../scripts/declare-dist-type.js" 36 | }, 37 | "license": "MIT", 38 | "author": "Rhys Evans", 39 | "bugs": { 40 | "url": "https://github.com/wheresrhys/fetch-mock/issues" 41 | }, 42 | "homepage": "http://www.wheresrhys.co.uk/fetch-mock", 43 | "keywords": [ 44 | "fetch", 45 | "http", 46 | "mock", 47 | "testing", 48 | "spy", 49 | "stub" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/IsSubsetOf.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /** Copied from deprecated https://www.npmjs.com/package/is-subset-of **/ 3 | import { Type } from './TypeDescriptor.js'; 4 | 5 | const allowedTypes = new Set(['array', 'object', 'function', 'null']); 6 | 7 | const isSubsetOf = function ( 8 | subset: any[] | Record | null, 9 | superset: any[] | Record | null, 10 | visited: string[] = [], 11 | ): boolean { 12 | const subsetType = Type.of(subset); 13 | const supersetType = Type.of(superset); 14 | 15 | if (!allowedTypes.has(subsetType)) { 16 | throw new Error(`Type '${subsetType}' is not supported.`); 17 | } 18 | if (!allowedTypes.has(supersetType)) { 19 | throw new Error(`Type '${supersetType}' is not supported.`); 20 | } 21 | 22 | if (Type.isFunction(subset)) { 23 | if (!Type.isFunction(superset)) { 24 | throw new Error( 25 | `Types '${subsetType}' and '${supersetType}' do not match.`, 26 | ); 27 | } 28 | 29 | return subset.toString() === superset.toString(); 30 | } 31 | 32 | if (Type.isArray(subset)) { 33 | if (!Type.isArray(superset)) { 34 | throw new Error( 35 | `Types '${subsetType}' and '${supersetType}' do not match.`, 36 | ); 37 | } 38 | if (subset.length > superset.length) { 39 | return false; 40 | } 41 | 42 | for (const subsetItem of subset) { 43 | const subsetItemType = Type.of(subsetItem); 44 | 45 | let isItemInSuperset; 46 | 47 | switch (subsetItemType) { 48 | case 'array': 49 | case 'object': 50 | case 'function': { 51 | if (visited.includes(subsetItem)) { 52 | continue; 53 | } 54 | 55 | visited.push(subsetItem); 56 | 57 | isItemInSuperset = superset.some((supersetItem: any): boolean => { 58 | try { 59 | return isSubsetOf(subsetItem, supersetItem, visited); 60 | } catch { 61 | return false; 62 | } 63 | }); 64 | break; 65 | } 66 | default: { 67 | isItemInSuperset = superset.includes(subsetItem); 68 | } 69 | } 70 | 71 | if (!isItemInSuperset) { 72 | return false; 73 | } 74 | } 75 | 76 | return true; 77 | } 78 | 79 | if (Type.isObject(subset)) { 80 | if (!Type.isObject(superset) || Type.isArray(superset)) { 81 | throw new Error( 82 | `Types '${subsetType}' and '${supersetType}' do not match.`, 83 | ); 84 | } 85 | if (Object.keys(subset).length > Object.keys(superset).length) { 86 | return false; 87 | } 88 | 89 | for (const [subsetKey, subsetValue] of Object.entries(subset)) { 90 | const supersetValue = superset[subsetKey]; 91 | 92 | const subsetValueType = Type.of(subsetValue); 93 | 94 | switch (subsetValueType) { 95 | case 'array': 96 | case 'object': 97 | case 'function': { 98 | if (visited.includes(subsetValue)) { 99 | continue; 100 | } 101 | 102 | visited.push(subsetValue); 103 | 104 | try { 105 | const isInSuperset = isSubsetOf( 106 | subsetValue, 107 | supersetValue, 108 | visited, 109 | ); 110 | 111 | if (!isInSuperset) { 112 | return false; 113 | } 114 | } catch { 115 | return false; 116 | } 117 | break; 118 | } 119 | default: { 120 | if (subsetValue !== supersetValue) { 121 | return false; 122 | } 123 | } 124 | } 125 | } 126 | 127 | return true; 128 | } 129 | 130 | if (Type.isNull(subset)) { 131 | if (!Type.isNull(superset)) { 132 | throw new Error( 133 | `Types '${subsetType}' and '${supersetType}' do not match.`, 134 | ); 135 | } 136 | 137 | return true; 138 | } 139 | 140 | throw new Error('Invalid operation.'); 141 | }; 142 | 143 | isSubsetOf.structural = function ( 144 | subset: Record | null, 145 | superset: Record | null, 146 | visited: string[] = [], 147 | ): boolean { 148 | if (!Type.isObject(subset)) { 149 | throw new Error(`Type '${Type.of(subset)}' is not supported.`); 150 | } 151 | 152 | if (!Type.isObject(superset)) { 153 | throw new Error(`Type '${Type.of(superset)}' is not supported.`); 154 | } 155 | 156 | for (const [subsetKey, subsetValue] of Object.entries(subset)) { 157 | if (superset[subsetKey] === undefined) { 158 | return false; 159 | } 160 | 161 | const subsetValueType = Type.of(subsetValue); 162 | const supersetValue = superset[subsetKey]; 163 | 164 | if (subsetValueType === 'object') { 165 | if (visited.includes(subsetValue)) { 166 | continue; 167 | } 168 | 169 | visited.push(subsetValue); 170 | 171 | try { 172 | const isInSuperset = isSubsetOf.structural( 173 | subsetValue, 174 | supersetValue, 175 | visited, 176 | ); 177 | 178 | if (!isInSuperset) { 179 | return false; 180 | } 181 | } catch { 182 | return false; 183 | } 184 | } 185 | } 186 | 187 | return true; 188 | }; 189 | 190 | export { isSubsetOf }; 191 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/RequestUtils.ts: -------------------------------------------------------------------------------- 1 | import type { CallLog } from './CallHistory.js'; 2 | // https://stackoverflow.com/a/19709846/308237 plus data: scheme 3 | // split into 2 code paths as URL constructor does not support protocol-relative urls 4 | const absoluteUrlRX = new RegExp('^[a-z]+://|^data:', 'i'); 5 | const protocolRelativeUrlRX = new RegExp('^//', 'i'); 6 | 7 | interface DerivedRequestOptions { 8 | method: string; 9 | body?: string; 10 | headers?: { [key: string]: string }; 11 | } 12 | 13 | export type NormalizedRequestOptions = 14 | | RequestInit 15 | | (RequestInit & DerivedRequestOptions); 16 | 17 | export function hasCredentialsInUrl(url: string): boolean { 18 | const urlObject = new URL( 19 | url, 20 | !absoluteUrlRX.test(url) ? 'http://dummy' : undefined, 21 | ); 22 | return Boolean(urlObject.username || urlObject.password); 23 | } 24 | 25 | export function normalizeUrl( 26 | url: string | String | URL, 27 | allowRelativeUrls: boolean, 28 | ) { 29 | if (url instanceof URL) { 30 | return url.href; 31 | } 32 | const primitiveUrl: string = String(url).valueOf(); 33 | 34 | if (absoluteUrlRX.test(primitiveUrl)) { 35 | return new URL(primitiveUrl).href; 36 | } 37 | if (protocolRelativeUrlRX.test(primitiveUrl)) { 38 | return new URL(primitiveUrl, 'http://dummy').href.replace(/^[a-z]+:/, ''); 39 | } 40 | 41 | if ('location' in globalThis) { 42 | if (primitiveUrl.startsWith('/')) { 43 | return `${globalThis.location.origin}${primitiveUrl}`; 44 | } else { 45 | return `${globalThis.location.href}/${primitiveUrl}`; 46 | } 47 | } else if (allowRelativeUrls) { 48 | const urlInstance = new URL(primitiveUrl, 'http://dummy'); 49 | return urlInstance.pathname + urlInstance.search; 50 | } else { 51 | throw new Error( 52 | 'Relative urls are not support by default in node.js tests. Either use a utility such as jsdom to define globalThis.location or set `fetchMock.config.allowRelativeUrls = true`', 53 | ); 54 | } 55 | } 56 | 57 | export function createCallLogFromUrlAndOptions( 58 | url: string | String | object, 59 | options: RequestInit, 60 | ): CallLog { 61 | const pendingPromises: Promise[] = []; 62 | if (typeof url === 'string' || url instanceof String || url instanceof URL) { 63 | const normalizedUrl: string = normalizeUrl(url, true); 64 | const derivedOptions = options ? { ...options } : {}; 65 | if (derivedOptions.headers) { 66 | derivedOptions.headers = normalizeHeaders(derivedOptions.headers); 67 | } 68 | derivedOptions.method = derivedOptions.method 69 | ? derivedOptions.method.toLowerCase() 70 | : 'get'; 71 | return { 72 | args: [url, options], 73 | url: normalizedUrl, 74 | queryParams: new URLSearchParams(getQuery(normalizedUrl)), 75 | options: derivedOptions, 76 | signal: derivedOptions.signal, 77 | pendingPromises, 78 | }; 79 | } 80 | if (typeof url === 'object') { 81 | throw new TypeError( 82 | 'fetch-mock: Unrecognised Request object. Read the Config and Installation sections of the docs', 83 | ); 84 | } else { 85 | throw new TypeError('fetch-mock: Invalid arguments passed to fetch'); 86 | } 87 | } 88 | 89 | export async function createCallLogFromRequest( 90 | request: Request, 91 | options: RequestInit, 92 | ): Promise { 93 | const pendingPromises: Promise[] = []; 94 | const derivedOptions: NormalizedRequestOptions = { 95 | method: request.method, 96 | }; 97 | 98 | try { 99 | try { 100 | derivedOptions.body = await request.clone().formData(); 101 | } catch { 102 | derivedOptions.body = await request.clone().text(); 103 | } 104 | } catch {} // eslint-disable-line no-empty 105 | if (request.headers) { 106 | derivedOptions.headers = normalizeHeaders(request.headers); 107 | } 108 | const url = normalizeUrl(request.url, true); 109 | const callLog = { 110 | args: [request, options], 111 | url, 112 | queryParams: new URLSearchParams(getQuery(url)), 113 | options: Object.assign(derivedOptions, options || {}), 114 | request: request, 115 | signal: (options && options.signal) || request.signal, 116 | pendingPromises, 117 | }; 118 | return callLog; 119 | } 120 | 121 | export function getPath(url: string): string { 122 | const u = absoluteUrlRX.test(url) 123 | ? new URL(url) 124 | : new URL(url, 'http://dummy'); 125 | return u.pathname; 126 | } 127 | 128 | export function getQuery(url: string): string { 129 | const u = absoluteUrlRX.test(url) 130 | ? new URL(url) 131 | : new URL(url, 'http://dummy'); 132 | return u.search ? u.search.substr(1) : ''; 133 | } 134 | 135 | export function normalizeHeaders( 136 | headers: HeadersInit | { [key: string]: string | number }, 137 | ): { [key: string]: string } { 138 | let entries; 139 | if (headers instanceof Headers) { 140 | entries = [...headers.entries()]; 141 | } else if (Array.isArray(headers)) { 142 | entries = headers; 143 | } else { 144 | entries = Object.entries(headers); 145 | } 146 | return Object.fromEntries( 147 | entries.map(([key, val]) => [key.toLowerCase(), String(val).valueOf()]), 148 | ); 149 | } 150 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/StatusTextMap.ts: -------------------------------------------------------------------------------- 1 | const statusTextMap: { [key: number]: string } = { 2 | 100: 'Continue', 3 | 101: 'Switching Protocols', 4 | 102: 'Processing', 5 | 200: 'OK', 6 | 201: 'Created', 7 | 202: 'Accepted', 8 | 203: 'Non-Authoritative Information', 9 | 204: 'No Content', 10 | 205: 'Reset Content', 11 | 206: 'Partial Content', 12 | 207: 'Multi-Status', 13 | 208: 'Already Reported', 14 | 226: 'IM Used', 15 | 300: 'Multiple Choices', 16 | 301: 'Moved Permanently', 17 | 302: 'Found', 18 | 303: 'See Other', 19 | 304: 'Not Modified', 20 | 305: 'Use Proxy', 21 | 307: 'Temporary Redirect', 22 | 308: 'Permanent Redirect', 23 | 400: 'Bad Request', 24 | 401: 'Unauthorized', 25 | 402: 'Payment Required', 26 | 403: 'Forbidden', 27 | 404: 'Not Found', 28 | 405: 'Method Not Allowed', 29 | 406: 'Not Acceptable', 30 | 407: 'Proxy Authentication Required', 31 | 408: 'Request Timeout', 32 | 409: 'Conflict', 33 | 410: 'Gone', 34 | 411: 'Length Required', 35 | 412: 'Precondition Failed', 36 | 413: 'Payload Too Large', 37 | 414: 'URI Too Long', 38 | 415: 'Unsupported Media Type', 39 | 416: 'Range Not Satisfiable', 40 | 417: 'Expectation Failed', 41 | 418: "I'm a teapot", 42 | 421: 'Misdirected Request', 43 | 422: 'Unprocessable Entity', 44 | 423: 'Locked', 45 | 424: 'Failed Dependency', 46 | 425: 'Unordered Collection', 47 | 426: 'Upgrade Required', 48 | 428: 'Precondition Required', 49 | 429: 'Too Many Requests', 50 | 431: 'Request Header Fields Too Large', 51 | 451: 'Unavailable For Legal Reasons', 52 | 500: 'Internal Server Error', 53 | 501: 'Not Implemented', 54 | 502: 'Bad Gateway', 55 | 503: 'Service Unavailable', 56 | 504: 'Gateway Timeout', 57 | 505: 'HTTP Version Not Supported', 58 | 506: 'Variant Also Negotiates', 59 | 507: 'Insufficient Storage', 60 | 508: 'Loop Detected', 61 | 509: 'Bandwidth Limit Exceeded', 62 | 510: 'Not Extended', 63 | 511: 'Network Authentication Required', 64 | }; 65 | 66 | export default statusTextMap; 67 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/TypeDescriptor.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-function-type */ 2 | /** Copied from deprecated https://www.npmjs.com/package/typedescriptor **/ 3 | const valueTypes = new Set([ 4 | 'boolean', 5 | 'number', 6 | 'null', 7 | 'string', 8 | 'undefined', 9 | ]); 10 | const referenceTypes = new Set(['array', 'function', 'object', 'symbol']); 11 | 12 | const detectableTypes = new Set([ 13 | 'boolean', 14 | 'function', 15 | 'number', 16 | 'string', 17 | 'symbol', 18 | ]); 19 | const typeConstructors = new Set([Boolean, Number, String]); 20 | 21 | class TypeDescriptor { 22 | public name: string; 23 | 24 | public isValueType: boolean; 25 | 26 | public isReferenceType: boolean; 27 | 28 | public isArray: boolean; 29 | 30 | public isBoolean: boolean; 31 | 32 | public isFunction: boolean; 33 | 34 | public isNull: boolean; 35 | 36 | public isNumber: boolean; 37 | 38 | public isObject: boolean; 39 | 40 | public isString: boolean; 41 | 42 | public isSymbol: boolean; 43 | 44 | public isUndefined: boolean; 45 | 46 | protected constructor(value: any) { 47 | this.name = TypeDescriptor.of(value); 48 | 49 | this.isValueType = TypeDescriptor.isValueType(value); 50 | this.isReferenceType = TypeDescriptor.isReferenceType(value); 51 | this.isArray = TypeDescriptor.isArray(value); 52 | this.isBoolean = TypeDescriptor.isBoolean(value); 53 | this.isFunction = TypeDescriptor.isFunction(value); 54 | this.isNull = TypeDescriptor.isNull(value); 55 | this.isNumber = TypeDescriptor.isNumber(value); 56 | this.isObject = TypeDescriptor.isObject(value); 57 | this.isString = TypeDescriptor.isString(value); 58 | this.isSymbol = TypeDescriptor.isSymbol(value); 59 | this.isUndefined = TypeDescriptor.isUndefined(value); 60 | } 61 | 62 | public static of(value: any): string { 63 | if (value === null) { 64 | return 'null'; 65 | } 66 | if (value === undefined) { 67 | return 'undefined'; 68 | } 69 | 70 | const detectedType = typeof value; 71 | 72 | if (detectableTypes.has(detectedType)) { 73 | return detectedType; 74 | } 75 | 76 | if (detectedType === 'object') { 77 | if (Array.isArray(value)) { 78 | return 'array'; 79 | } 80 | 81 | if (typeConstructors.has(value.constructor)) { 82 | return value.constructor.name.toLowerCase(); 83 | } 84 | 85 | return detectedType; 86 | } 87 | 88 | throw new Error('Failed due to an unknown type.'); 89 | } 90 | 91 | public static from(value: any): TypeDescriptor { 92 | return new TypeDescriptor(value); 93 | } 94 | 95 | public static isValueType( 96 | value: any, 97 | ): value is boolean | number | null | string | undefined { 98 | return valueTypes.has(TypeDescriptor.of(value)); 99 | } 100 | 101 | public static isReferenceType( 102 | value: any, 103 | ): value is any[] | Function | object | symbol { 104 | return referenceTypes.has(TypeDescriptor.of(value)); 105 | } 106 | 107 | public static isArray(value: any): value is any[] { 108 | return TypeDescriptor.of(value) === 'array'; 109 | } 110 | 111 | public static isBoolean(value: any): value is boolean { 112 | return TypeDescriptor.of(value) === 'boolean'; 113 | } 114 | 115 | public static isFunction(value: any): value is Function { 116 | return TypeDescriptor.of(value) === 'function'; 117 | } 118 | 119 | public static isNull(value: any): value is null { 120 | return TypeDescriptor.of(value) === 'null'; 121 | } 122 | 123 | public static isNumber(value: any): value is number { 124 | return TypeDescriptor.of(value) === 'number'; 125 | } 126 | 127 | public static isObject(value: any): value is object { 128 | return TypeDescriptor.of(value) === 'object'; 129 | } 130 | 131 | public static isString(value: any): value is string { 132 | return TypeDescriptor.of(value) === 'string'; 133 | } 134 | 135 | public static isSymbol(value: any): value is symbol { 136 | return TypeDescriptor.of(value) === 'symbol'; 137 | } 138 | 139 | public static isUndefined(value: any): value is undefined { 140 | return TypeDescriptor.of(value) === 'undefined'; 141 | } 142 | } 143 | 144 | export { TypeDescriptor as Type }; 145 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/__tests__/FetchMock/flush.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, beforeEach } from 'vitest'; 2 | 3 | import fetchMock from '../../FetchMock'; 4 | describe('FetchMockWrapper.js', () => { 5 | describe('flushing pending calls', () => { 6 | let fm; 7 | beforeEach(() => { 8 | fm = fetchMock.createInstance(); 9 | }); 10 | 11 | it('flush resolves if all fetches have resolved', async () => { 12 | fm.route('http://one.com/', 200).route('http://two.com/', 200); 13 | // no expectation, but if it doesn't work then the promises will hang 14 | // or reject and the test will timeout 15 | await fm.callHistory.flush(); 16 | fm.fetchHandler('http://one.com'); 17 | await fm.callHistory.flush(); 18 | fm.fetchHandler('http://two.com'); 19 | await fm.callHistory.flush(); 20 | }); 21 | 22 | it('should resolve after fetches', async () => { 23 | fm.route('http://example', 'working!'); 24 | let data; 25 | fm.fetchHandler('http://example').then(() => { 26 | data = 'done'; 27 | }); 28 | await fm.callHistory.flush(); 29 | expect(data).toEqual('done'); 30 | }); 31 | 32 | describe('response methods', () => { 33 | it('should resolve after .text() if waitForResponseMethods option passed', async () => { 34 | fm.route('http://example/', 'working!'); 35 | let data = 'not set'; 36 | fm.fetchHandler('http://example/').then(async (res) => { 37 | await res.text(); 38 | data = 'done'; 39 | }); 40 | 41 | await fm.callHistory.flush(true); 42 | expect(data).toEqual('done'); 43 | }); 44 | it('should resolve after .json() if waitForResponseMethods option passed', async () => { 45 | fm.route('http://example/', { a: 'ok' }); 46 | let data; 47 | fm.fetchHandler('http://example/') 48 | .then((res) => res.json()) 49 | .then(() => { 50 | data = 'done'; 51 | }); 52 | 53 | await fm.callHistory.flush(true); 54 | expect(data).toEqual('done'); 55 | }); 56 | 57 | it('should resolve after .json() if waitForResponseMethods option passed, but contains invalid json', async () => { 58 | fm.route('http://example/', 'bleurgh'); 59 | let data; 60 | fm.fetchHandler('http://example/') 61 | .then((res) => res.json()) 62 | .catch(() => { 63 | data = 'done'; 64 | }); 65 | 66 | await fm.callHistory.flush(true); 67 | expect(data).toEqual('done'); 68 | }); 69 | }); 70 | 71 | it('flush waits for unresolved promises', async () => { 72 | fm.route('http://one.com/', 200).route( 73 | 'http://two.com/', 74 | () => new Promise((res) => setTimeout(() => res(200), 50)), 75 | ); 76 | 77 | const orderedResults = []; 78 | fm.fetchHandler('http://one.com/'); 79 | fm.fetchHandler('http://two.com/'); 80 | 81 | setTimeout(() => orderedResults.push('not flush'), 25); 82 | 83 | await fm.callHistory.flush(); 84 | orderedResults.push('flush'); 85 | expect(orderedResults).toEqual(['not flush', 'flush']); 86 | }); 87 | 88 | it('flush resolves on expected error', async () => { 89 | fm.route('http://one.com/', { throws: 'Problem in space' }); 90 | await fm.callHistory.flush(); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/__tests__/FetchMock/mock-and-spy.test.js: -------------------------------------------------------------------------------- 1 | import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'; 2 | import fetchMock from '../../FetchMock'; 3 | 4 | describe('mock and spy', () => { 5 | let fm; 6 | const nativeFetch = globalThis.fetch; 7 | beforeEach(() => { 8 | fm = fetchMock.createInstance(); 9 | }); 10 | afterEach(() => { 11 | globalThis.fetch = nativeFetch; 12 | }); 13 | 14 | const testChainableMethod = (method, ...args) => { 15 | it(`${method}() is chainable`, () => { 16 | expect(fm[method](...args)).toEqual(fm); 17 | }); 18 | 19 | it(`${method}() has "this"`, () => { 20 | vi.spyOn(fm, method).mockReturnThis(); 21 | expect(fm[method](...args)).toBe(fm); 22 | fm[method].mockRestore(); 23 | }); 24 | }; 25 | 26 | describe('.mockGlobal()', () => { 27 | testChainableMethod('mockGlobal'); 28 | testChainableMethod('unmockGlobal'); 29 | 30 | it('replaces global fetch with fetchMock.fetchHandler', async () => { 31 | vi.spyOn(fm, 'fetchHandler'); 32 | fm.mockGlobal(); 33 | try { 34 | await fetch('http://a.com', { method: 'post' }); 35 | } catch {} //eslint-disable-line no-empty 36 | // cannot just check globalThis.fetch === fm.fetchHandler because we apply .bind() to fetchHandler 37 | expect(fm.fetchHandler).toHaveBeenCalledWith('http://a.com', { 38 | method: 'post', 39 | }); 40 | }); 41 | 42 | it('calls to fetch are successfully handled by fetchMock.fetchHandler', async () => { 43 | fm.mockGlobal().catch(200); 44 | const response = await fetch('http://a.com', { method: 'post' }); 45 | expect(response.status).toEqual(200); 46 | const callLog = fm.callHistory.lastCall(); 47 | expect(callLog.args).toEqual(['http://a.com', { method: 'post' }]); 48 | }); 49 | 50 | it('restores global fetch', () => { 51 | fm.mockGlobal().unmockGlobal(); 52 | expect(globalThis.fetch).toEqual(nativeFetch); 53 | }); 54 | 55 | it('does not error when called on unmocked fetch', () => { 56 | expect(() => fm.unmockGlobal()).not.toThrow(); 57 | }); 58 | }); 59 | describe('.spy()', () => { 60 | testChainableMethod('spy'); 61 | testChainableMethod('spyGlobal'); 62 | 63 | it('passes all requests through to the network by default', async () => { 64 | vi.spyOn(fm.config, 'fetch'); 65 | fm.spy(); 66 | try { 67 | await fm.fetchHandler('http://a.com/', { method: 'post' }); 68 | } catch {} //eslint-disable-line no-empty 69 | expect(fm.config.fetch).toHaveBeenCalledWith('http://a.com/', { 70 | method: 'post', 71 | }); 72 | fm.config.fetch.mockRestore(); 73 | }); 74 | it('falls through to network for a specific route', async () => { 75 | vi.spyOn(fm.config, 'fetch'); 76 | fm.spy('http://a.com').route('http://b.com', 200); 77 | try { 78 | await fm.fetchHandler('http://a.com/', { method: 'post' }); 79 | await fm.fetchHandler('http://b.com/', { method: 'post' }); 80 | } catch {} //eslint-disable-line no-empty 81 | 82 | expect(fm.config.fetch).toHaveBeenCalledTimes(1); 83 | expect(fm.config.fetch).toHaveBeenCalledWith('http://a.com/', { 84 | method: 'post', 85 | }); 86 | fm.config.fetch.mockRestore(); 87 | }); 88 | 89 | it('can apply the full range of matchers and route options', async () => { 90 | vi.spyOn(fm.config, 'fetch'); 91 | fm.spy({ method: 'delete', headers: { check: 'this' } }).catch(); 92 | try { 93 | await fm.fetchHandler('http://a.com/'); 94 | await fm.fetchHandler('http://a.com/', { 95 | method: 'delete', 96 | headers: { check: 'this' }, 97 | }); 98 | } catch {} //eslint-disable-line no-empty 99 | expect(fm.config.fetch).toHaveBeenCalledTimes(1); 100 | expect(fm.config.fetch).toHaveBeenCalledWith('http://a.com/', { 101 | method: 'delete', 102 | headers: { check: 'this' }, 103 | }); 104 | fm.config.fetch.mockRestore(); 105 | }); 106 | 107 | it('can name a route', async () => { 108 | fm.spy('http://a.com/', 'myroute').catch(); 109 | try { 110 | await fm.fetchHandler('http://a.com/'); 111 | } catch {} //eslint-disable-line no-empty 112 | expect(fm.callHistory.called('myroute')).toBe(true); 113 | }); 114 | 115 | it('plays nice with mockGlobal()', async () => { 116 | globalThis.fetch = fm.config.fetch = vi.fn(); 117 | fm.mockGlobal().spy('http://a.com', 200); 118 | try { 119 | await fm.fetchHandler('http://a.com/', { method: 'post' }); 120 | } catch {} //eslint-disable-line no-empty 121 | expect(fm.config.fetch).toHaveBeenCalledTimes(1); 122 | expect(fm.config.fetch).toHaveBeenCalledWith('http://a.com/', { 123 | method: 'post', 124 | }); 125 | }); 126 | 127 | it('has spyGlobal() shorthand', async () => { 128 | globalThis.fetch = fm.config.fetch = vi.fn(); 129 | fm.spyGlobal(); 130 | try { 131 | await fm.fetchHandler('http://a.com/', { method: 'post' }); 132 | } catch {} //eslint-disable-line no-empty 133 | expect(fm.config.fetch).toHaveBeenCalledTimes(1); 134 | expect(fm.config.fetch).toHaveBeenCalledWith('http://a.com/', { 135 | method: 'post', 136 | }); 137 | }); 138 | 139 | it('can call actual native fetch without erroring', async () => { 140 | fm.spyGlobal(); 141 | const isBrowser = Boolean(globalThis.location); 142 | // avoids getting caught by a cors error 143 | const testUrl = isBrowser ? '/' : 'http://example.com/'; 144 | await expect(fm.fetchHandler(testUrl)).resolves.toBeInstanceOf(Response); 145 | }); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/__tests__/Matchers/express.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import Route from '../../Route'; 3 | 4 | describe('express path parameter matching', () => { 5 | it('can match a path parameters', () => { 6 | const route = new Route({ 7 | url: 'express:/type/:instance', 8 | response: 200, 9 | params: { instance: 'b' }, 10 | }); 11 | expect(route.matcher({ url: '/' })).toBe(false); 12 | expect(route.matcher({ url: '/type/a' })).toBe(false); 13 | expect(route.matcher({ url: '/type/b' })).toBe(true); 14 | }); 15 | 16 | it('can match multiple path parameters', () => { 17 | const route = new Route({ 18 | url: 'express:/:type/:instance', 19 | response: 200, 20 | params: { instance: 'b', type: 'cat' }, 21 | }); 22 | expect(route.matcher({ url: '/' })).toBe(false); 23 | expect(route.matcher({ url: '/dog/a' })).toBe(false); 24 | expect(route.matcher({ url: '/cat/a' })).toBe(false); 25 | expect(route.matcher({ url: '/dog/b' })).toBe(false); 26 | expect(route.matcher({ url: '/cat/b' })).toBe(true); 27 | }); 28 | 29 | it('can match a path parameter on a full url', () => { 30 | const route = new Route({ 31 | url: 'express:/type/:instance', 32 | response: 200, 33 | params: { instance: 'b' }, 34 | }); 35 | expect(route.matcher({ url: 'http://site.com/' })).toBe(false); 36 | expect(route.matcher({ url: 'http://site.com/type/a' })).toBe(false); 37 | expect(route.matcher({ url: 'http://site.com/type/b' })).toBe(true); 38 | }); 39 | 40 | it('can match fully qualified url', () => { 41 | const route = new Route({ url: 'express:/apps/:id', response: 200 }); 42 | 43 | expect(route.matcher({ url: 'https://api.example.com/apps/abc' })).toBe( 44 | true, 45 | ); 46 | }); 47 | 48 | it('can match based on the existence, not value, of a parameter', () => { 49 | const route = new Route({ 50 | url: 'express:/type/:instance', 51 | response: 200, 52 | }); 53 | expect(route.matcher({ url: '/nottype/a' })).toBe(false); 54 | expect(route.matcher({ url: '/type/a' })).toBe(true); 55 | }); 56 | it('writes parameter values to the callLog', () => { 57 | const route = new Route({ 58 | url: 'express:/type/:instance', 59 | response: 200, 60 | params: { instance: 'b' }, 61 | }); 62 | const callLog = { url: '/type/a' }; 63 | route.matcher(callLog); 64 | expect(callLog.expressParams).toEqual({ instance: 'a' }); 65 | 66 | const callLog2 = { url: '/type/b' }; 67 | route.matcher(callLog2); 68 | expect(callLog2.expressParams).toEqual({ instance: 'b' }); 69 | }); 70 | 71 | it('writes parameter values to the callLog even if not matched on', () => { 72 | const route = new Route({ 73 | url: 'express:/type/:instance', 74 | response: 200, 75 | }); 76 | const callLog = { url: '/type/a' }; 77 | route.matcher(callLog); 78 | expect(callLog.expressParams).toEqual({ instance: 'a' }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/__tests__/Matchers/function.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import Route from '../../Route'; 3 | 4 | describe('function matching', () => { 5 | it('match using custom function', () => { 6 | const route = new Route({ 7 | matcherFunction: ({ url, options }) => 8 | url.indexOf('logged-in') > -1 && 9 | options && 10 | options.headers && 11 | options.headers.authorized === true, 12 | response: 200, 13 | }); 14 | 15 | expect( 16 | route.matcher({ 17 | url: 'http://a.com/12345', 18 | options: { 19 | headers: { authorized: true }, 20 | }, 21 | }), 22 | ).toBe(false); 23 | expect(route.matcher({ url: 'http://a.com/logged-in' })).toBe(false); 24 | expect( 25 | route.matcher({ 26 | url: 'http://a.com/logged-in', 27 | options: { 28 | headers: { authorized: true }, 29 | }, 30 | }), 31 | ).toBe(true); 32 | }); 33 | 34 | it('match using custom function using request body', () => { 35 | const route = new Route({ 36 | matcherFunction: (req) => { 37 | return req.options.body === 'a string'; 38 | }, 39 | response: 200, 40 | }); 41 | expect(route.matcher({ url: 'http://a.com/logged-in', options: {} })).toBe( 42 | false, 43 | ); 44 | expect( 45 | route.matcher({ 46 | url: 'http://a.com/logged-in', 47 | options: { 48 | method: 'post', 49 | body: 'a string', 50 | }, 51 | }), 52 | ).toBe(true); 53 | }); 54 | 55 | it('match using custom function alongside other matchers', () => { 56 | const route = new Route({ 57 | url: 'end:profile', 58 | response: 200, 59 | matcherFunction: ({ options }) => 60 | options && options.headers && options.headers.authorized === true, 61 | }); 62 | 63 | expect(route.matcher({ url: 'http://a.com/profile' })).toBe(false); 64 | expect( 65 | route.matcher({ 66 | url: 'http://a.com/not', 67 | options: { 68 | headers: { authorized: true }, 69 | }, 70 | }), 71 | ).toBe(false); 72 | expect( 73 | route.matcher({ 74 | url: 'http://a.com/profile', 75 | options: { 76 | headers: { authorized: true }, 77 | }, 78 | }), 79 | ).toBe(true); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/__tests__/Matchers/method.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import Route from '../../Route'; 4 | 5 | describe('method matching', () => { 6 | it('match any method by default', () => { 7 | const route = new Route({ url: '*', response: 200 }); 8 | 9 | expect( 10 | route.matcher({ url: 'http://a.com/', options: { method: 'GET' } }), 11 | ).toBe(true); 12 | expect( 13 | route.matcher({ url: 'http://a.com/', options: { method: 'POST' } }), 14 | ).toBe(true); 15 | }); 16 | 17 | it('configure an exact method to match', () => { 18 | const route = new Route({ method: 'POST', response: 200 }); 19 | 20 | expect( 21 | route.matcher({ url: 'http://a.com/', options: { method: 'GET' } }), 22 | ).toBe(false); 23 | expect( 24 | route.matcher({ url: 'http://a.com/', options: { method: 'POST' } }), 25 | ).toBe(true); 26 | }); 27 | 28 | it('match implicit GET', () => { 29 | const route = new Route({ method: 'GET', response: 200 }); 30 | expect(route.matcher({ url: 'http://a.com/' })).toBe(true); 31 | }); 32 | 33 | it('be case insensitive', () => { 34 | const upperCaseRoute = new Route({ method: 'POST', response: 200 }); 35 | const lowerCaseRoute = new Route({ method: 'post', response: 200 }); 36 | 37 | expect( 38 | upperCaseRoute.matcher({ 39 | url: 'http://a.com/', 40 | options: { method: 'post' }, 41 | }), 42 | ).toBe(true); 43 | expect( 44 | upperCaseRoute.matcher({ 45 | url: 'http://a.com/', 46 | options: { method: 'POST' }, 47 | }), 48 | ).toBe(true); 49 | expect( 50 | lowerCaseRoute.matcher({ 51 | url: 'http://a.com/', 52 | options: { method: 'post' }, 53 | }), 54 | ).toBe(true); 55 | expect( 56 | lowerCaseRoute.matcher({ 57 | url: 'http://a.com/', 58 | options: { method: 'POST' }, 59 | }), 60 | ).toBe(true); 61 | }); 62 | 63 | it('can be used alongside function matchers', () => { 64 | const route = new Route({ 65 | method: 'POST', 66 | matcherFunction: ({ url }) => /a\.com/.test(url), 67 | 68 | response: 200, 69 | }); 70 | 71 | expect(route.matcher({ url: 'http://a.com' })).toBe(false); 72 | expect( 73 | route.matcher({ url: 'http://b.com', options: { method: 'POST' } }), 74 | ).toBe(false); 75 | expect( 76 | route.matcher({ url: 'http://a.com', options: { method: 'POST' } }), 77 | ).toBe(true); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/__tests__/Matchers/route-config-object.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import Route from '../../Route'; 3 | 4 | // TODO should this whole thing be integration tests on router 5 | // as it's mainly about the shape of optiosn passed into to addRoute 6 | describe('matcher object', () => { 7 | it('use matcher object with matcher property', () => { 8 | const route = new Route({ url: 'http://a.com', response: 200 }); 9 | expect(route.matcher({ url: 'http://a.com' })).toBe(true); 10 | }); 11 | 12 | it('use matcher object with url property', () => { 13 | const route = new Route({ url: 'http://a.com', response: 200 }); 14 | expect(route.matcher({ url: 'http://a.com' })).toBe(true); 15 | }); 16 | 17 | it('can use function and url simultaneously', () => { 18 | const route = new Route({ 19 | url: 'end:path', 20 | matcherFunction: ({ options }) => 21 | options && options.headers && options.headers.authorized === true, 22 | response: 200, 23 | }); 24 | 25 | expect(route.matcher({ url: 'http://a.com/path' })).toBe(false); 26 | expect( 27 | route.matcher({ 28 | url: 'http://a.com', 29 | options: { 30 | headers: { authorized: true }, 31 | }, 32 | }), 33 | ).toBe(false); 34 | expect( 35 | route.matcher({ 36 | url: 'http://a.com/path', 37 | options: { 38 | headers: { authorized: true }, 39 | }, 40 | }), 41 | ).toBe(true); 42 | }); 43 | 44 | // TODO this shoudl probably be an error 45 | it('if no url provided, error', () => { 46 | expect(() => new Route({ response: 200 })).toThrowError( 47 | "fetch-mock: Each route must specify some criteria for matching calls to fetch. To match all calls use '*'", 48 | ); 49 | }); 50 | 51 | it('can match Headers', () => { 52 | const route = new Route({ 53 | url: 'http://a.com', 54 | headers: { a: 'b' }, 55 | response: 200, 56 | }); 57 | 58 | expect( 59 | route.matcher({ 60 | url: 'http://a.com', 61 | options: { 62 | headers: { a: 'c' }, 63 | }, 64 | }), 65 | ).toBe(false); 66 | expect( 67 | route.matcher({ 68 | url: 'http://a.com', 69 | options: { 70 | headers: { a: 'b' }, 71 | }, 72 | }), 73 | ).toBe(true); 74 | }); 75 | 76 | it('can match query string', () => { 77 | const route = new Route({ 78 | url: 'http://a.com', 79 | query: { a: 'b' }, 80 | response: 200, 81 | }); 82 | 83 | expect( 84 | route.matcher({ 85 | url: 'http://a.com', 86 | queryParams: new URLSearchParams(''), 87 | }), 88 | ).toBe(false); 89 | expect( 90 | route.matcher({ 91 | url: 'http://a.com', 92 | queryParams: new URLSearchParams('a=b'), 93 | }), 94 | ).toBe(true); 95 | }); 96 | 97 | it('can match path parameter', () => { 98 | const route = new Route({ 99 | url: 'express:/type/:var', 100 | params: { var: 'b' }, 101 | response: 200, 102 | }); 103 | expect(route.matcher({ url: '/' })).toBe(false); 104 | expect(route.matcher({ url: '/type/a' })).toBe(false); 105 | expect(route.matcher({ url: '/type/b' })).toBe(true); 106 | }); 107 | 108 | it('can match method', () => { 109 | const route = new Route({ method: 'POST', response: 200 }); 110 | expect( 111 | route.matcher({ url: 'http://a.com', options: { method: 'GET' } }), 112 | ).toBe(false); 113 | expect( 114 | route.matcher({ url: 'http://a.com', options: { method: 'POST' } }), 115 | ).toBe(true); 116 | }); 117 | 118 | it('can match body', () => { 119 | const route = new Route({ body: { foo: 'bar' }, response: 200 }); 120 | 121 | expect( 122 | route.matcher({ 123 | url: 'http://a.com', 124 | options: { 125 | method: 'POST', 126 | }, 127 | }), 128 | ).toBe(false); 129 | expect( 130 | route.matcher({ 131 | url: 'http://a.com', 132 | options: { 133 | method: 'POST', 134 | body: JSON.stringify({ foo: 'bar' }), 135 | headers: { 'Content-Type': 'application/json' }, 136 | }, 137 | }), 138 | ).toBe(true); 139 | }); 140 | 141 | it('support setting matchPartialBody on matcher parameter', () => { 142 | const route = new Route({ 143 | body: { a: 1 }, 144 | matchPartialBody: true, 145 | response: 200, 146 | }); 147 | expect( 148 | route.matcher({ 149 | url: 'http://a.com', 150 | options: { 151 | method: 'POST', 152 | body: JSON.stringify({ a: 1, b: 2 }), 153 | }, 154 | }), 155 | ).toBe(true); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/__tests__/spec-compliance.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, beforeAll } from 'vitest'; 2 | import fetchMock from '../FetchMock'; 3 | describe('Spec compliance', () => { 4 | // NOTE: these are not exhaustive, but feel like a sensible, reasonably easy to implement subset 5 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#exceptions 6 | describe('exceptions', () => { 7 | beforeAll(() => fetchMock.catch()); 8 | it('reject on invalid header name', async () => { 9 | await expect( 10 | fetchMock.fetchHandler('http://a.com', { 11 | headers: { 12 | 'has space': 'ok', 13 | }, 14 | }), 15 | ).rejects.toThrow(new TypeError('Invalid name')); 16 | }); 17 | it('reject on url containing credentials', async () => { 18 | await expect( 19 | fetchMock.fetchHandler('http://user:password@a.com'), 20 | ).rejects.toThrow( 21 | new TypeError( 22 | 'Request cannot be constructed from a URL that includes credentials: http://user:password@a.com/', 23 | ), 24 | ); 25 | }); 26 | it('rejects on protocol agnostic url containing credentials', async () => { 27 | await expect( 28 | fetchMock.fetchHandler('//user:password@a.com'), 29 | ).rejects.toThrow( 30 | new TypeError( 31 | 'Request cannot be constructed from a URL that includes credentials: //user:password@a.com/', 32 | ), 33 | ); 34 | }); 35 | it('reject if the request method is GET or HEAD and the body is non-null.', async () => { 36 | await expect( 37 | fetchMock.fetchHandler('http://a.com', { body: 'a' }), 38 | ).rejects.toThrow( 39 | new TypeError('Request with GET/HEAD method cannot have body.'), 40 | ); 41 | await expect( 42 | fetchMock.fetchHandler('http://a.com', { body: 'a', method: 'GET' }), 43 | ).rejects.toThrow( 44 | new TypeError('Request with GET/HEAD method cannot have body.'), 45 | ); 46 | await expect( 47 | fetchMock.fetchHandler('http://a.com', { body: 'a', method: 'HEAD' }), 48 | ).rejects.toThrow( 49 | new TypeError('Request with GET/HEAD method cannot have body.'), 50 | ); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/fetch-mock/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | FetchMockConfig, 3 | FetchMock, 4 | defaultFetchMockConfig, 5 | } from './FetchMock.js'; 6 | export { CallHistoryFilter, CallLog } from './CallHistory.js'; 7 | export { RouteMatcher, MatcherDefinition } from './Matchers.js'; 8 | export { 9 | UserRouteConfig, 10 | RouteResponse, 11 | RouteName, 12 | ModifyRouteConfig, 13 | } from './Route.js'; 14 | export { RemoveRouteOptions } from './Router.js'; 15 | 16 | import fetchMock from './FetchMock.js'; 17 | export default fetchMock; 18 | -------------------------------------------------------------------------------- /packages/fetch-mock/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules", "src/__tests__"], 5 | "rootDir": "src", 6 | "compilerOptions": { 7 | "esModuleInterop": true, 8 | "moduleResolution": "node", 9 | "module": "commonjs", 10 | "outDir": "dist/cjs", 11 | "target": "es2021" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/fetch-mock/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules", "src/__tests__"], 5 | "rootDir": "src", 6 | "compilerOptions": { 7 | "module": "nodenext", 8 | "outDir": "dist/esm", 9 | "target": "es2021" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/jest/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rhys Evans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/jest/README.md: -------------------------------------------------------------------------------- 1 | # @fetch-mock/jest 2 | 3 | A wrapper for fetch-mock that improves the developer experience when working with jest. It provides the following: 4 | 5 | - Adds methods to fetchMock which wrap its default methods, but align more closely with jest's naming conventions. 6 | - Extends `expect` with convenience methods allowing for expressive tests such as `expect(fetchMock).toHavePosted('http://example.com', {id: 'test-id'})`. 7 | - Can optionally be hooked in to jest's global mock management methods such as `clearAllMocks()`. 8 | 9 | ## Requirements 10 | 11 | @fetch-mock/jest requires either of the following to run: 12 | 13 | - [jest](https://jest.dev/guide/) 14 | - The `fetch` API, via one of the following: 15 | - [Node.js](https://nodejs.org/) 18+ for full feature operation 16 | - Any modern browser that supports the `fetch` API 17 | - [node-fetch](https://www.npmjs.com/package/node-fetch) when testing in earlier versions of Node.js (this is untested, but should mostly work) 18 | 19 | ## Documentation and Usage 20 | 21 | See the [project website](https://www.wheresrhys.co.uk/fetch-mock/docs/wrappers/jest/) 22 | 23 | ## License 24 | 25 | @fetch-mock/vitest is licensed under the [MIT](https://github.com/wheresrhys/fetch-mock/blob/master/LICENSE) license. 26 | Copyright © 2024, Rhys Evans 27 | -------------------------------------------------------------------------------- /packages/jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fetch-mock/jest", 3 | "description": "jest wrapper for fetch-mock", 4 | "version": "0.2.15", 5 | "exports": { 6 | "browser": "./dist/esm/index.js", 7 | "import": { 8 | "types": "./dist/esm/index.d.ts", 9 | "default": "./dist/esm/index.js" 10 | }, 11 | "require": { 12 | "types": "./dist/cjs/index.d.ts", 13 | "default": "./dist/cjs/index.js" 14 | } 15 | }, 16 | "main": "./dist/cjs/index.js", 17 | "module": "./dist/esm/index.js", 18 | "types": "./dist/esm/index.d.ts", 19 | "type": "module", 20 | "engines": { 21 | "node": ">=18.11.0" 22 | }, 23 | "dependencies": { 24 | "fetch-mock": "^12.5.2" 25 | }, 26 | "peerDependencies": { 27 | "jest": "*", 28 | "@jest/globals": "*" 29 | }, 30 | "repository": { 31 | "directory": "packages/jest", 32 | "type": "git", 33 | "url": "git+https://github.com/wheresrhys/fetch-mock.git" 34 | }, 35 | "scripts": { 36 | "build": "rm -rf dist && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node ../../scripts/declare-dist-type.js" 37 | }, 38 | "license": "MIT", 39 | "author": "Rhys Evans", 40 | "bugs": { 41 | "url": "https://github.com/wheresrhys/fetch-mock/issues" 42 | }, 43 | "homepage": "http://www.wheresrhys.co.uk/fetch-mock", 44 | "keywords": [ 45 | "fetch", 46 | "http", 47 | "mock", 48 | "testing", 49 | "spy", 50 | "stub", 51 | "jest" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /packages/jest/src/__tests__/custom-jsdom-environment.ts: -------------------------------------------------------------------------------- 1 | import { TestEnvironment } from 'jest-environment-jsdom'; 2 | 3 | export default class CustomTestEnvironment extends TestEnvironment { 4 | async setup() { 5 | await super.setup(); 6 | 7 | this.global.Request = Request; 8 | this.global.Response = Response; 9 | this.global.ReadableStream = ReadableStream; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/jest/src/__tests__/jsdom.spec.ts: -------------------------------------------------------------------------------- 1 | import './custom-jsdom-environment'; 2 | import fetchMock from '../index'; 3 | import { describe, it, expect } from '@jest/globals'; 4 | 5 | fetchMock.mockGlobal().get('http://example.com', { status: 418 }); 6 | 7 | describe('compatibility with jsdom', () => { 8 | it('can fetch stuff', async () => { 9 | const r = await fetch('http://example.com'); 10 | expect(r.status).toEqual(418); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/jest/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FetchMock, 3 | defaultFetchMockConfig, 4 | RemoveRouteOptions, 5 | } from 'fetch-mock'; 6 | import './jest-extensions.js'; 7 | import type { Jest } from '@jest/environment'; 8 | import type { FetchMockMatchers } from './types.js'; 9 | export { FetchMockMatchers } from './types.js'; 10 | 11 | type MockResetOptions = { 12 | includeSticky: boolean; 13 | }; 14 | 15 | class FetchMockJest extends FetchMock { 16 | mockClear() { 17 | this.clearHistory(); 18 | return this; 19 | } 20 | mockReset(options: MockResetOptions = { includeSticky: false }) { 21 | this.removeRoutes({ 22 | ...options, 23 | includeFallback: true, 24 | } as RemoveRouteOptions); 25 | return this.mockClear(); 26 | } 27 | mockRestore(options?: MockResetOptions) { 28 | this.unmockGlobal(); 29 | return this.mockReset(options); 30 | } 31 | } 32 | 33 | export function manageFetchMockGlobally(jest: Jest) { 34 | const { clearAllMocks, resetAllMocks, restoreAllMocks } = jest; 35 | 36 | jest.clearAllMocks = () => { 37 | clearAllMocks.apply(jest); 38 | fetchMockJest.mockClear(); 39 | return jest; 40 | }; 41 | 42 | jest.resetAllMocks = () => { 43 | resetAllMocks.apply(jest); 44 | fetchMockJest.mockReset(); 45 | return jest; 46 | }; 47 | 48 | jest.restoreAllMocks = () => { 49 | restoreAllMocks.apply(jest); 50 | fetchMockJest.mockRestore(); 51 | return jest; 52 | }; 53 | } 54 | 55 | const fetchMockJest = new FetchMockJest({ 56 | ...defaultFetchMockConfig, 57 | }); 58 | 59 | export default fetchMockJest; 60 | 61 | /* eslint-disable @typescript-eslint/no-namespace */ 62 | /** 63 | * Export types on the expect object 64 | */ 65 | declare global { 66 | namespace jest { 67 | // Type-narrow expect for FetchMock 68 | interface Expect { 69 | (actual: FetchMock): FetchMockMatchers & { 70 | not: FetchMockMatchers; 71 | }; 72 | (actual: typeof fetch): FetchMockMatchers & { 73 | not: FetchMockMatchers; 74 | }; 75 | } 76 | } 77 | } 78 | /* eslint-enable @typescript-eslint/no-namespace */ 79 | 80 | declare module '@jest/expect' { 81 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type 82 | interface Matchers extends FetchMockMatchers {} 83 | } 84 | -------------------------------------------------------------------------------- /packages/jest/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | CallHistoryFilter, 3 | FetchMock, 4 | RouteName, 5 | UserRouteConfig, 6 | } from 'fetch-mock'; 7 | import type { SyncExpectationResult } from 'expect'; 8 | 9 | export type HumanVerbs = 10 | | 'Got' 11 | | 'Posted' 12 | | 'Put' 13 | | 'Deleted' 14 | | 'FetchedHead' 15 | | 'Patched' 16 | | 'Fetched'; 17 | 18 | /** 19 | * Verify that a particular call for the HTTP method implied in the function name 20 | * has occurred 21 | */ 22 | export type ToHaveFunc = ( 23 | filter?: CallHistoryFilter, 24 | options?: UserRouteConfig, 25 | ) => R; 26 | 27 | /** 28 | * Verify that a particular Nth call for the HTTP method implied in the function name 29 | * has occurred 30 | */ 31 | export type ToHaveNthFunc = ( 32 | n: number, 33 | filter?: CallHistoryFilter, 34 | options?: UserRouteConfig, 35 | ) => R; 36 | 37 | /** 38 | * Verify that a particular call for the HTTP method implied in the function name 39 | * has been made N times 40 | */ 41 | export type ToHaveTimesFunc = ( 42 | times: number, 43 | filter?: CallHistoryFilter, 44 | options?: UserRouteConfig, 45 | ) => R; 46 | 47 | /** 48 | * Verify that a particular route names(s) has been called 49 | */ 50 | export type ToBeDoneFunc = (routes?: RouteName | RouteName[]) => R; 51 | 52 | export type FetchMockMatchers = { 53 | toHaveFetched: ToHaveFunc; 54 | toHaveLastFetched: ToHaveFunc; 55 | toHaveFetchedTimes: ToHaveTimesFunc; 56 | toHaveNthFetched: ToHaveNthFunc; 57 | toHaveGot: ToHaveFunc; 58 | toHaveLastGot: ToHaveFunc; 59 | toHaveGotTimes: ToHaveTimesFunc; 60 | toHaveNthGot: ToHaveNthFunc; 61 | toHavePosted: ToHaveFunc; 62 | toHaveLastPosted: ToHaveFunc; 63 | toHavePostedTimes: ToHaveTimesFunc; 64 | toHaveNthPosted: ToHaveNthFunc; 65 | toHavePut: ToHaveFunc; 66 | toHaveLastPut: ToHaveFunc; 67 | toHavePutTimes: ToHaveTimesFunc; 68 | toHaveNthPut: ToHaveNthFunc; 69 | toHaveDeleted: ToHaveFunc; 70 | toHaveLastDeleted: ToHaveFunc; 71 | toHaveDeletedTimes: ToHaveTimesFunc; 72 | toHaveNthDeleted: ToHaveNthFunc; 73 | toHaveFetchedHead: ToHaveFunc; 74 | toHaveLastFetchedHead: ToHaveFunc; 75 | toHaveFetchedHeadTimes: ToHaveTimesFunc; 76 | toHaveNthFetchedHead: ToHaveNthFunc; 77 | toHavePatched: ToHaveFunc; 78 | toHaveLastPatched: ToHaveFunc; 79 | toHavePatchedTimes: ToHaveTimesFunc; 80 | toHaveNthPatched: ToHaveNthFunc; 81 | toBeDone: ToBeDoneFunc; 82 | }; 83 | 84 | // types for use doing some intermediate type checking in extensions to make sure things don't get out of sync 85 | /** 86 | * This reflects the Object.assign that FetchMock does on the fetch function 87 | */ 88 | export type PatchedFetch = { 89 | fetchMock: FetchMock; 90 | }; 91 | 92 | /** 93 | * This type allows us to take the Matcher type and creat another one 94 | */ 95 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 96 | type RawMatcher any> = ( 97 | input: PatchedFetch | FetchMock, 98 | ...args: Parameters 99 | ) => ReturnType; 100 | 101 | export type RawFetchMockMatchers = { 102 | [k in keyof FetchMockMatchers]: RawMatcher< 103 | FetchMockMatchers[k] 104 | >; 105 | }; 106 | 107 | export type HumanVerbMethodNames = 108 | | `toHave${M}` 109 | | `toHaveLast${M}` 110 | | `toHave${M}Times` 111 | | `toHaveNth${M}`; 112 | -------------------------------------------------------------------------------- /packages/jest/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules", "src/__tests__"], 5 | "rootDir": "src", 6 | "compilerOptions": { 7 | "esModuleInterop": true, 8 | "moduleResolution": "node", 9 | "module": "commonjs", 10 | "outDir": "dist/cjs", 11 | "target": "es2021" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/jest/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules", "src/__tests__"], 5 | "rootDir": "src", 6 | "compilerOptions": { 7 | "module": "nodenext", 8 | "outDir": "dist/esm", 9 | "target": "es2021" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/vitest/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rhys Evans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/vitest/README.md: -------------------------------------------------------------------------------- 1 | # @fetch-mock/vitest 2 | 3 | A wrapper for fetch-mock that improves the developer experience when working with vitest. It provides the following: 4 | 5 | - Adds methods to fetchMock which wrap its default methods, but align more closely with vitest's naming conventions. 6 | - Extends `expect` with convenience methods allowing for expressive tests such as `expect(fetchMock).toHavePosted('http://example.com', {id: 'test-id'})`. 7 | - Can optionally be hooked in to vitest's global mock management methods such as `clearAllMocks()`. 8 | 9 | ## Requirements 10 | 11 | @fetch-mock/vitest requires either of the following to run: 12 | 13 | - [vitest](https://vitest.dev/guide/) 14 | - The `fetch` API, via one of the following: 15 | - [Node.js](https://nodejs.org/) 18+ for full feature operation 16 | - Any modern browser that supports the `fetch` API 17 | - [node-fetch](https://www.npmjs.com/package/node-fetch) when testing in earlier versions of Node.js (this is untested, but should mostly work) 18 | 19 | ## Documentation and Usage 20 | 21 | See the [project website](https://www.wheresrhys.co.uk/fetch-mock/docs/wrappers/vitest/) 22 | 23 | ## License 24 | 25 | @fetch-mock/vitest is licensed under the [MIT](https://github.com/wheresrhys/fetch-mock/blob/master/LICENSE) license. 26 | Copyright © 2024, Rhys Evans 27 | -------------------------------------------------------------------------------- /packages/vitest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fetch-mock/vitest", 3 | "description": "Vitest wrapper for fetch-mock", 4 | "version": "0.2.13", 5 | "exports": { 6 | "browser": "./dist/esm/index.js", 7 | "import": { 8 | "types": "./dist/esm/index.d.ts", 9 | "default": "./dist/esm/index.js" 10 | }, 11 | "require": { 12 | "types": "./dist/cjs/index.d.ts", 13 | "default": "./dist/cjs/index.js" 14 | } 15 | }, 16 | "main": "./dist/cjs/index.js", 17 | "module": "./dist/esm/index.js", 18 | "types": "./dist/esm/index.d.ts", 19 | "type": "module", 20 | "engines": { 21 | "node": ">=18.11.0" 22 | }, 23 | "dependencies": { 24 | "fetch-mock": "^12.5.2" 25 | }, 26 | "peerDependencies": { 27 | "vitest": "*" 28 | }, 29 | "repository": { 30 | "directory": "packages/vitest", 31 | "type": "git", 32 | "url": "git+https://github.com/wheresrhys/fetch-mock.git" 33 | }, 34 | "scripts": { 35 | "build": "rm -rf dist && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node ../../scripts/declare-dist-type.js" 36 | }, 37 | "license": "MIT", 38 | "author": "Rhys Evans", 39 | "bugs": { 40 | "url": "https://github.com/wheresrhys/fetch-mock/issues" 41 | }, 42 | "homepage": "http://www.wheresrhys.co.uk/fetch-mock", 43 | "keywords": [ 44 | "fetch", 45 | "http", 46 | "mock", 47 | "testing", 48 | "spy", 49 | "stub", 50 | "vitest" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /packages/vitest/src/index.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest'; 2 | import { 3 | FetchMock, 4 | defaultFetchMockConfig, 5 | RemoveRouteOptions, 6 | } from 'fetch-mock'; 7 | import './vitest-extensions.js'; 8 | export type { FetchMockMatchers } from './types.js'; 9 | 10 | type MockResetOptions = { 11 | includeSticky: boolean; 12 | }; 13 | 14 | class FetchMockVitest extends FetchMock { 15 | mockClear() { 16 | this.clearHistory(); 17 | return this; 18 | } 19 | mockReset(options: MockResetOptions = { includeSticky: false }) { 20 | this.removeRoutes({ 21 | ...options, 22 | includeFallback: true, 23 | } as RemoveRouteOptions); 24 | return this.mockClear(); 25 | } 26 | mockRestore(options?: MockResetOptions) { 27 | this.unmockGlobal(); 28 | return this.mockReset(options); 29 | } 30 | } 31 | 32 | export function manageFetchMockGlobally() { 33 | const { clearAllMocks, resetAllMocks, restoreAllMocks, unstubAllGlobals } = 34 | vi; 35 | 36 | vi.clearAllMocks = () => { 37 | clearAllMocks.apply(vi); 38 | fetchMockVitest.mockClear(); 39 | return vi; 40 | }; 41 | 42 | vi.resetAllMocks = () => { 43 | resetAllMocks.apply(vi); 44 | fetchMockVitest.mockReset(); 45 | return vi; 46 | }; 47 | 48 | vi.restoreAllMocks = () => { 49 | restoreAllMocks.apply(vi); 50 | fetchMockVitest.mockRestore(); 51 | return vi; 52 | }; 53 | 54 | vi.unstubAllGlobals = () => { 55 | unstubAllGlobals.apply(vi); 56 | fetchMockVitest.mockRestore(); 57 | return vi; 58 | }; 59 | } 60 | 61 | const fetchMockVitest = new FetchMockVitest({ 62 | ...defaultFetchMockConfig, 63 | }); 64 | 65 | export default fetchMockVitest; 66 | -------------------------------------------------------------------------------- /packages/vitest/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | CallHistoryFilter, 3 | FetchMock, 4 | RouteName, 5 | UserRouteConfig, 6 | } from 'fetch-mock'; 7 | import type { SyncExpectationResult } from 'expect'; 8 | 9 | export type HumanVerbs = 10 | | 'Got' 11 | | 'Posted' 12 | | 'Put' 13 | | 'Deleted' 14 | | 'FetchedHead' 15 | | 'Patched' 16 | | 'Fetched'; 17 | 18 | /** 19 | * Verify that a particular call for the HTTP method implied in the function name 20 | * has occurred 21 | */ 22 | export type ToHaveFunc = ( 23 | filter?: CallHistoryFilter, 24 | options?: UserRouteConfig, 25 | ) => R; 26 | 27 | /** 28 | * Verify that a particular Nth call for the HTTP method implied in the function name 29 | * has occurred 30 | */ 31 | export type ToHaveNthFunc = ( 32 | n: number, 33 | filter?: CallHistoryFilter, 34 | options?: UserRouteConfig, 35 | ) => R; 36 | 37 | /** 38 | * Verify that a particular call for the HTTP method implied in the function name 39 | * has been made N times 40 | */ 41 | export type ToHaveTimesFunc = ( 42 | times: number, 43 | filter?: CallHistoryFilter, 44 | options?: UserRouteConfig, 45 | ) => R; 46 | 47 | /** 48 | * Verify that a particular route names(s) has been called 49 | */ 50 | export type ToBeDoneFunc = (routes?: RouteName | RouteName[]) => R; 51 | 52 | export type FetchMockMatchers = { 53 | toHaveFetched: ToHaveFunc; 54 | toHaveLastFetched: ToHaveFunc; 55 | toHaveFetchedTimes: ToHaveTimesFunc; 56 | toHaveNthFetched: ToHaveNthFunc; 57 | toHaveGot: ToHaveFunc; 58 | toHaveLastGot: ToHaveFunc; 59 | toHaveGotTimes: ToHaveTimesFunc; 60 | toHaveNthGot: ToHaveNthFunc; 61 | toHavePosted: ToHaveFunc; 62 | toHaveLastPosted: ToHaveFunc; 63 | toHavePostedTimes: ToHaveTimesFunc; 64 | toHaveNthPosted: ToHaveNthFunc; 65 | toHavePut: ToHaveFunc; 66 | toHaveLastPut: ToHaveFunc; 67 | toHavePutTimes: ToHaveTimesFunc; 68 | toHaveNthPut: ToHaveNthFunc; 69 | toHaveDeleted: ToHaveFunc; 70 | toHaveLastDeleted: ToHaveFunc; 71 | toHaveDeletedTimes: ToHaveTimesFunc; 72 | toHaveNthDeleted: ToHaveNthFunc; 73 | toHaveFetchedHead: ToHaveFunc; 74 | toHaveLastFetchedHead: ToHaveFunc; 75 | toHaveFetchedHeadTimes: ToHaveTimesFunc; 76 | toHaveNthFetchedHead: ToHaveNthFunc; 77 | toHavePatched: ToHaveFunc; 78 | toHaveLastPatched: ToHaveFunc; 79 | toHavePatchedTimes: ToHaveTimesFunc; 80 | toHaveNthPatched: ToHaveNthFunc; 81 | toBeDone: ToBeDoneFunc; 82 | }; 83 | 84 | // types for use doing some intermediate type checking in extensions to make sure things don't get out of sync 85 | /** 86 | * This reflects the Object.assign that FetchMock does on the fetch function 87 | */ 88 | export type PatchedFetch = { 89 | fetchMock: FetchMock; 90 | }; 91 | 92 | /** 93 | * This type allows us to take the Matcher type and creat another one 94 | */ 95 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 96 | type RawMatcher any> = ( 97 | input: PatchedFetch | FetchMock, 98 | ...args: Parameters 99 | ) => ReturnType; 100 | 101 | export type RawFetchMockMatchers = { 102 | [k in keyof FetchMockMatchers]: RawMatcher< 103 | FetchMockMatchers[k] 104 | >; 105 | }; 106 | 107 | export type HumanVerbMethodNames = 108 | | `toHave${M}` 109 | | `toHaveLast${M}` 110 | | `toHave${M}Times` 111 | | `toHaveNth${M}`; 112 | -------------------------------------------------------------------------------- /packages/vitest/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules", "src/__tests__"], 5 | "rootDir": "src", 6 | "compilerOptions": { 7 | "esModuleInterop": true, 8 | "moduleResolution": "node", 9 | "module": "commonjs", 10 | "outDir": "dist/cjs", 11 | "target": "es2021" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/vitest/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules", "src/__tests__"], 5 | "rootDir": "src", 6 | "compilerOptions": { 7 | "module": "nodenext", 8 | "outDir": "dist/esm", 9 | "target": "es2021" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "release-type": "node", 4 | "separate-pull-requests": false, 5 | "changelog-type": "default", 6 | "changelog-sections": [ 7 | { 8 | "type": "feat", 9 | "section": "Features", 10 | "hidden": false 11 | }, 12 | { 13 | "type": "fix", 14 | "section": "Bug Fixes", 15 | "hidden": false 16 | }, 17 | { 18 | "type": "docs", 19 | "section": "Documentation Changes", 20 | "hidden": false 21 | }, 22 | { 23 | "type": "chore", 24 | "section": "Miscellaneous", 25 | "hidden": true 26 | } 27 | ], 28 | 29 | "plugins": ["node-workspace"], 30 | "packages": { 31 | "packages/codemods": {}, 32 | "packages/vitest": {}, 33 | "packages/jest": {}, 34 | "packages/fetch-mock": {} 35 | }, 36 | "bootstrap-sha": "812f462efde5ade292394b94c9f2cbe0aedf8e3f", 37 | "pull-request-title-pattern": "build${scope}: release${component} ${version}", 38 | "pull-request-header": ":rock: I've created a release for you", 39 | "prerelease": true, 40 | "bump-minor-pre-major": true, 41 | "bump-patch-for-minor-pre-major": true 42 | } 43 | -------------------------------------------------------------------------------- /scripts/circleci-npm-publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | pattern='^([[:alnum:]-]*)-v[[:digit:]]{1,}\.[[:digit:]]{1,}\.[[:digit:]]{1,}(-[[:alpha:]]{1,}\.[[:digit:]]{1,}){0,1}$' 6 | if [[ ! $CIRCLE_TAG =~ $pattern ]]; then 7 | echo "cannot parse git tag $CIRCLE_TAG from CircleCI" 8 | exit 1 9 | fi 10 | 11 | workspace=${BASH_REMATCH[1]} 12 | if [[ $workspace = 'fetch-mock-monorepo' ]]; then 13 | echo "top-level fetch-mock-monorepo package should not be published" 14 | exit 1 15 | else 16 | workspace="packages/$workspace" 17 | fi 18 | 19 | NPM_PUBLISH_TAG=$([[ $CIRCLE_TAG =~ -[a-z-]+$ ]] && echo "pre-release" || echo "latest") 20 | 21 | npm publish --workspace "$workspace" --access=public --tag $NPM_PUBLISH_TAG 22 | -------------------------------------------------------------------------------- /scripts/declare-dist-type.js: -------------------------------------------------------------------------------- 1 | import { writeFile } from 'fs/promises'; 2 | const longFormatNames = { 3 | cjs: 'commonjs', 4 | esm: 'module', 5 | }; 6 | 7 | async function createPackageJson(format) { 8 | const pkg = { type: longFormatNames[format] }; 9 | 10 | const location = `./dist/${format}`; 11 | await writeFile(`${location}/package.json`, JSON.stringify(pkg, null, 2)); 12 | } 13 | 14 | await createPackageJson('esm'); 15 | await createPackageJson('cjs'); 16 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xa93409b6Dd2fd3Aa55812E183E5B191fa3562e9c' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "inlineSourceMap": false, 4 | "listEmittedFiles": false, 5 | "listFiles": false, 6 | "pretty": true, 7 | "traceResolution": false, 8 | "lib": ["es2021", "dom", "dom.iterable"], 9 | "moduleResolution": "nodenext", 10 | "allowJs": true, 11 | "checkJs": true, 12 | "strict": true, 13 | "skipLibCheck": true, 14 | "noImplicitAny": true, 15 | "noImplicitThis": true, 16 | "strictNullChecks": false, 17 | "strictFunctionTypes": true, 18 | "downlevelIteration": true, 19 | "forceConsistentCasingInFileNames": true, 20 | "types": [], 21 | "declaration": true, 22 | "removeComments": true 23 | }, 24 | "compileOnSave": false 25 | } 26 | --------------------------------------------------------------------------------