├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ ├── nodejs.yml │ └── publish.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── LICENSE.md ├── README.md ├── package.json ├── setupJest.js ├── src └── index.js ├── tests ├── api.js ├── node.test.js └── test.js ├── tsconfig.json ├── types ├── index.d.ts ├── test.ts └── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | root = true 3 | end_of_line = lf 4 | insert_final_newline = true 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | chaset = utf-8 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.ts] 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | node: true, 6 | }, 7 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | ecmaVersion: 6, 11 | }, 12 | plugins: ['@typescript-eslint'], 13 | rules: { 14 | '@typescript-eslint/no-explicit-any': 0, 15 | '@typescript-eslint/no-var-requires': 0, 16 | '@typescript-eslint/no-unused-vars': 0, 17 | 'no-global-assign': 0, 18 | 'no-undef': 0, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # then publish the resulting package to NPM js 3 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 4 | 5 | name: Node.js CI 6 | 7 | on: 8 | push: 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [16.x, 18.x, 20.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install 28 | - run: npm test 29 | env: 30 | CI: true 31 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies and publishes via npm 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js Publish CI 5 | 6 | on: 7 | pull_request: 8 | types: [ closed ] 9 | branches: [ master ] 10 | 11 | jobs: 12 | build: 13 | if: github.event.pull_request.merged == true 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 20.x 22 | registry-url: 'https://registry.npmjs.org' 23 | - run: npm install 24 | env: 25 | CI: true 26 | - run: npm publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_API_KEY }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | yarn-error.log 4 | coverage 5 | .idea 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests 2 | yarn.lock 3 | node_modules 4 | *.log 5 | coverage 6 | .idea 7 | .vscode 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | types/test.ts 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jeff Lau 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jest Fetch Mock 2 | 3 | ![npm downloads](https://img.shields.io/npm/dw/jest-fetch-mock) 4 | ![Node.js CI](https://github.com/jefflau/jest-fetch-mock/workflows/Node.js%20CI/badge.svg) 5 | 6 | Fetch is the canonical way to do HTTP requests in the browser, and it can be used in other environments such as React Native. Jest Fetch Mock allows you to easily mock your `fetch` calls and return the response you need to fake the HTTP requests. It's easy to setup and you don't need a library like `nock` to get going and it uses Jest's built-in support for mocking under the surface. This means that any of the `jest.fn()` methods are also available. For more information on the jest mock API, check their docs [here](https://facebook.github.io/jest/docs/en/mock-functions.html) 7 | 8 | It currently supports the mocking with the [`cross-fetch`](https://www.npmjs.com/package/cross-fetch) polyfill, so it supports Node.js and any browser-like runtime. 9 | 10 | ## Contents 11 | 12 | - [Usage](#usage) 13 | - [Installation and Setup](#package-installation) 14 | - [Using with Create-React-App](#using-with-create-react-app) 15 | - [API](#api) 16 | - [Examples](#examples) 17 | - [Simple mock and assert](#simple-mock-and-assert) 18 | - [Mocking all fetches](#mocking-all-fetches) 19 | - [Mocking a failed fetch](#mocking-a-failed-fetch) 20 | - [Mocking multiple fetches with different responses](#mocking-multiple-fetches-with-different-responses) 21 | - [Mocking multiple fetches with `fetch.mockResponses`](#mocking-multiple-fetches-with-fetchmockresponses) 22 | - [Reset mocks between tests with `fetch.resetMocks`](#reset-mocks-between-tests-with-fetchresetmocks) 23 | - [Using `fetch.mock` to inspect the mock state of each fetch call](#using-fetchmock-to-inspect-the-mock-state-of-each-fetch-call) 24 | - [Using functions to mock slow servers](#using-functions-to-mock-slow-servers) 25 | 26 | ## Usage 27 | 28 | ### Package Installation 29 | 30 | To setup your fetch mock you need to do the following things: 31 | 32 | ``` 33 | $ npm install --save-dev jest-fetch-mock 34 | ``` 35 | 36 | Create a `setupJest` file to setup the mock or add this to an existing `setupFile`. : 37 | 38 | ### To setup for all tests 39 | 40 | ```js 41 | //setupJest.js or similar file 42 | require('jest-fetch-mock').enableMocks() 43 | ``` 44 | 45 | Add the setupFile to your jest config in `package.json`: 46 | 47 | ```JSON 48 | "jest": { 49 | "automock": false, 50 | "resetMocks": false, 51 | "setupFiles": [ 52 | "./setupJest.js" 53 | ] 54 | } 55 | ``` 56 | 57 | With this done, you'll have `fetch` and `fetchMock` available on the global scope. Fetch will be used as usual by your code and you'll use `fetchMock` in your tests 58 | 59 | Note: the `resetMocks` Jest configuration default was changed from `false` to `true` in Jest 4.0.1. Therefore the Jest configuration of setting it to `false` is required if the `setupJest.js` is what calls "enableMocks()" (i.e. in the above suggested setup) otherwise you will need to call "enableMocks()" in a "beforeEach" block. 60 | 61 | #### Default not mocked 62 | 63 | If you would like to have the 'fetchMock' available in all tests but not enabled then add `fetchMock.dontMock()` after the `...enableMocks()` line in `setupJest.js`: 64 | 65 | ```js 66 | // adds the 'fetchMock' global variable and rewires 'fetch' global to call 'fetchMock' instead of the real implementation 67 | require('jest-fetch-mock').enableMocks() 68 | // changes default behavior of fetchMock to use the real 'fetch' implementation and not mock responses 69 | fetchMock.dontMock() 70 | ``` 71 | 72 | If you want a single test file to return to the default behavior of mocking all responses, add the following to the 73 | test file: 74 | 75 | ```js 76 | beforeEach(() => { 77 | // if you have an existing `beforeEach` just add the following line to it 78 | fetchMock.doMock() 79 | }) 80 | ``` 81 | 82 | To enable mocking for a specific URL only: 83 | 84 | ```js 85 | beforeEach(() => { 86 | // if you have an existing `beforeEach` just add the following lines to it 87 | fetchMock.mockIf(/^https?:\/\/example.com.*$/, async (req) => { 88 | if (req.url.endsWith('/path1')) { 89 | return 'some response body' 90 | } else if (req.url.endsWith('/path2')) { 91 | return { 92 | body: 'another response body', 93 | headers: { 94 | 'X-Some-Response-Header': 'Some header value' 95 | } 96 | } 97 | } else { 98 | return { 99 | status: 404, 100 | body: 'Not Found' 101 | } 102 | } 103 | }) 104 | }) 105 | ``` 106 | 107 | If you have changed the default behavior to use the real implementation, you can guarantee the next call to fetch 108 | will be mocked by using the `mockOnce` function: 109 | 110 | ```js 111 | fetchMock.mockOnce('the next call to fetch will always return this as the body') 112 | ``` 113 | 114 | This function behaves exactly like `fetchMock.once` but guarantees the next call to `fetch` will be mocked even if the 115 | default behavior of fetchMock is to use the real implementation. You can safely convert all you `fetchMock.once` calls 116 | to `fetchMock.mockOnce` without a risk of changing their behavior. 117 | 118 | ### To setup for an individual test 119 | 120 | For JavaScript add the following line to the start of your test case (before any other requires) 121 | 122 | ```js 123 | require('jest-fetch-mock').enableMocks() 124 | ``` 125 | 126 | For TypeScript/ES6 add the following lines to the start of your test case (before any other imports) 127 | 128 | ```typescript 129 | import { enableFetchMocks } from 'jest-fetch-mock' 130 | enableFetchMocks() 131 | ``` 132 | 133 | #### TypeScript importing 134 | 135 | If you are using TypeScript and receive errors about the `fetchMock` global not existing, 136 | add a `global.d.ts` file to the root of your project (or add the following line to an existing global file): 137 | 138 | ```typescript 139 | import 'jest-fetch-mock' 140 | ``` 141 | 142 | If you prefer you can also just import the fetchMock in a test case. 143 | 144 | ```typescript 145 | import fetchMock from 'jest-fetch-mock' 146 | ``` 147 | 148 | You may also need to edit your `tsconfig.json` and add "dom" and/or "es2015" and/or "esnext" to the 'compilerConfig.lib' property 149 | 150 | ### Using with Create-React-App 151 | 152 | If you are using [Create-React-App](https://github.com/facebookincubator/create-react-app) (CRA), the code for `setupJest.js` above should be placed into `src/setupTests.js` in the root of your project. CRA automatically uses this filename by convention in the Jest configuration it generates. Similarly, changing to your `package.json` is not required as CRA handles this when generating your Jest configuration. 153 | 154 | ### For Ejected Create React Apps _ONLY_: 155 | 156 | > Note: Keep in mind that if you decide to "eject" before creating src/setupTests.js, the resulting package.json file won't contain any reference to it, so you should manually create the property setupTestFrameworkScriptFile in the configuration for Jest, something like the [following](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#srcsetuptestsjs-1): 157 | 158 | ```JSON 159 | "jest": { 160 | "setupTestFrameworkScriptFile": "/src/setupTests.js" 161 | } 162 | ``` 163 | 164 | ## API 165 | 166 | ### Mock Responses 167 | 168 | - `fetch.mockResponse(bodyOrFunction, init): fetch` - Mock all fetch calls 169 | - `fetch.mockResponseOnce(bodyOrFunction, init): fetch` - Mock each fetch call independently 170 | - `fetch.once(bodyOrFunction, init): fetch` - Alias for `mockResponseOnce(bodyOrFunction, init)` 171 | - `fetch.mockResponses(...responses): fetch` - Mock multiple fetch calls independently 172 | - Each argument is an array taking `[bodyOrFunction, init]` 173 | - `fetch.mockReject(errorOrFunction): fetch` - Mock all fetch calls, letting them fail directly 174 | - `fetch.mockRejectOnce(errorOrFunction): fetch` - Let the next fetch call fail directly 175 | - `fetch.mockAbort(): fetch` - Causes all fetch calls to reject with an "Aborted!" error 176 | - `fetch.mockAbortOnce(): fetch` - Causes the next fetch call to reject with an "Aborted!" error 177 | 178 | ### Functions 179 | 180 | Instead of passing body, it is also possible to pass a function that returns a promise. 181 | The promise should resolve with a string or an object containing body and init props 182 | 183 | i.e: 184 | 185 | ```js 186 | fetch.mockResponse(() => callMyApi().then(res => ({ body: 'ok' }))) 187 | // OR 188 | fetch.mockResponse(() => callMyApi().then(res => 'ok')) 189 | ``` 190 | 191 | The function may take an optional "request" parameter of type `http.Request`: 192 | 193 | ```js 194 | fetch.mockResponse(req => 195 | req.url === 'http://myapi/' 196 | ? callMyApi().then(res => 'ok') 197 | : Promise.reject(new Error('bad url')) 198 | ) 199 | ``` 200 | 201 | Note: the request "url" is parsed and then printed using the equivalent of `new URL(input).href` so it may not match exactly with the URL's passed to `fetch` if they are not fully qualified. 202 | For example, passing "http://foo.com" to `fetch` will result in the request URL being "http://foo.com/" (note the trailing slash). 203 | 204 | The same goes for rejects: 205 | 206 | ```js 207 | fetch.mockReject(() => 208 | doMyAsyncJob().then(res => Promise.reject(res.errorToRaise)) 209 | ) 210 | // OR 211 | fetch.mockReject(req => 212 | req.headers.get('content-type') === 'text/plain' 213 | ? Promise.reject('invalid content type') 214 | : doMyAsyncJob().then(res => Promise.reject(res.errorToRaise)) 215 | ) 216 | ``` 217 | 218 | ### Mock utilities 219 | 220 | - `fetch.resetMocks()` - Clear previously set mocks so they do not bleed into other mocks 221 | - `fetch.enableMocks()` - Enable fetch mocking by overriding `global.fetch` and mocking `node-fetch` 222 | - `fetch.disableMocks()` - Disable fetch mocking and restore default implementation of `fetch` and/or `node-fetch` 223 | - `fetch.mock` - The mock state for your fetch calls. Make assertions on the arguments given to `fetch` when called by the functions you are testing. For more information check the [Jest docs](https://facebook.github.io/jest/docs/en/mock-functions.html#mock-property) 224 | 225 | For information on the arguments body and init can take, you can look at the MDN docs on the Response Constructor function, which `jest-fetch-mock` uses under the surface. 226 | 227 | https://developer.mozilla.org/en-US/docs/Web/API/Response/Response 228 | 229 | Each mocked response or err 230 | or will return a [Mock Function](http://facebook.github.io/jest/docs/mock-function-api.html#content). You can use methods like `.toHaveBeenCalledWith` to ensure that the mock function was called with specific arguments. For more methods detail, take a look at [this](http://facebook.github.io/jest/docs/expect.html#content). 231 | 232 | ## Examples 233 | 234 | In most of the complicated examples below, I am testing my action creators in Redux, but it doesn't have to be used with Redux. 235 | 236 | ### Simple mock and assert 237 | 238 | In this simple example I won't be using any libraries. It is a simple fetch request, in this case to google.com. First we setup the `beforeEach` callback to reset our mocks. This isn't strictly necessary in this example, but since we will probably be mocking fetch more than once, we need to reset it across our tests to assert on the arguments given to fetch. Make sure the function wrapping your test is marked as async. 239 | 240 | Once we've done that we can start to mock our response. We want to give it an object with a `data` property and a string value of `12345` and wrap it in `JSON.stringify` to JSONify it. Here we use `mockResponseOnce`, but we could also use `once`, which is an alias for a call to `mockResponseOnce`. 241 | 242 | We then call the function that we want to test with the arguments we want to test with. We use `await` to wait until the response resolves, and then assert we have got the correct data back. 243 | 244 | Finally we can assert on the `.mock` state that Jest provides for us to test what arguments were given to fetch and how many times it was called 245 | 246 | ```js 247 | //api.js 248 | export function APIRequest(who) { 249 | if (who === 'google') { 250 | return fetch('https://google.com').then(res => res.json()) 251 | } else { 252 | return 'no argument provided' 253 | } 254 | } 255 | ``` 256 | 257 | ```js 258 | //api.test.js 259 | import { APIRequest } from './api' 260 | 261 | describe('testing api', () => { 262 | beforeEach(() => { 263 | fetch.resetMocks() 264 | }) 265 | 266 | it('calls google and returns data to me', async () => { 267 | fetch.mockResponseOnce(JSON.stringify({ data: '12345' })) 268 | 269 | //assert on the response 270 | const res = await APIRequest('google') 271 | expect(res.data).toEqual('12345') 272 | 273 | //assert on the times called and arguments given to fetch 274 | expect(fetch.mock.calls.length).toEqual(1) 275 | expect(fetch.mock.calls[0][0]).toEqual('https://google.com') 276 | }) 277 | }) 278 | ``` 279 | 280 | ### Mocking all fetches 281 | 282 | In this example I am mocking just one fetch call. Any additional fetch calls in the same function will also have the same mock response. For more complicated functions with multiple fetch calls, you can check out example 3. 283 | 284 | ```js 285 | import configureMockStore from 'redux-mock-store' // mock store 286 | import thunk from 'redux-thunk' 287 | 288 | const middlewares = [thunk] 289 | const mockStore = configureMockStore(middlewares) 290 | 291 | import { getAccessToken } from './accessToken' 292 | 293 | describe('Access token action creators', () => { 294 | it('dispatches the correct actions on successful fetch request', () => { 295 | fetch.mockResponse(JSON.stringify({ access_token: '12345' })) 296 | 297 | const expectedActions = [ 298 | { type: 'SET_ACCESS_TOKEN', token: { access_token: '12345' } } 299 | ] 300 | const store = mockStore({ config: { token: '' } }) 301 | 302 | return ( 303 | store 304 | .dispatch(getAccessToken()) 305 | //getAccessToken contains the fetch call 306 | .then(() => { 307 | // return of async actions 308 | expect(store.getActions()).toEqual(expectedActions) 309 | }) 310 | ) 311 | }) 312 | }) 313 | ``` 314 | 315 | ### Mocking a failed fetch 316 | 317 | In this example I am mocking just one fetch call but this time using the `mockReject` function to simulate a failed request. Any additional fetch calls in the same function will also have the same mock response. For more complicated functions with multiple fetch calls, you can check out example 3. 318 | 319 | ```js 320 | import configureMockStore from 'redux-mock-store' // mock store 321 | import thunk from 'redux-thunk' 322 | 323 | const middlewares = [thunk] 324 | const mockStore = configureMockStore(middlewares) 325 | 326 | import { getAccessToken } from './accessToken' 327 | 328 | describe('Access token action creators', () => { 329 | it('dispatches the correct actions on a failed fetch request', () => { 330 | fetch.mockReject(new Error('fake error message')) 331 | 332 | const expectedActions = [ 333 | { type: 'SET_ACCESS_TOKEN_FAILED', error: { status: 503 } } 334 | ] 335 | const store = mockStore({ config: { token: '' } }) 336 | 337 | return ( 338 | store 339 | .dispatch(getAccessToken()) 340 | //getAccessToken contains the fetch call 341 | .then(() => { 342 | // return of async actions 343 | expect(store.getActions()).toEqual(expectedActions) 344 | }) 345 | ) 346 | }) 347 | }) 348 | ``` 349 | 350 | ### Mocking aborted fetches 351 | 352 | Fetches can be mocked to act as if they were aborted during the request. This can be done in 4 ways: 353 | 354 |
    355 |
  1. Using `fetch.mockAbort()`
  2. 356 |
  3. Using `fetch.mockAbortOnce()`
  4. 357 |
  5. Passing a request (or request init) with a 'signal' to fetch that has been aborted
  6. 358 |
  7. Passing a request (or request init) with a 'signal' to fetch and a async function to `fetch.mockResponse` or `fetch.mockResponseOnce` that causes the signal to abort before returning the response
  8. 359 |
360 | 361 | ```js 362 | describe('Mocking aborts', () => { 363 | beforeEach(() => { 364 | fetch.resetMocks() 365 | fetch.doMock() 366 | jest.useFakeTimers() 367 | }) 368 | afterEach(() => { 369 | jest.useRealTimers() 370 | }) 371 | 372 | it('rejects with an Aborted! Error', () => { 373 | fetch.mockAbort() 374 | expect(fetch('/')).rejects.toThrow('Aborted!') 375 | }) 376 | it('rejects with an Aborted! Error once then mocks with empty response', async () => { 377 | fetch.mockAbortOnce() 378 | await expect(fetch('/')).rejects.toThrow('Aborted!') 379 | await expect(request()).resolves.toEqual('') 380 | }) 381 | 382 | it('throws when passed an already aborted abort signal', () => { 383 | const c = new AbortController() 384 | c.abort() 385 | expect(() => fetch('/', { signal: c.signal })).toThrow('Aborted!') 386 | }) 387 | 388 | it('rejects when aborted before resolved', async () => { 389 | const c = new AbortController() 390 | fetch.mockResponse(async () => { 391 | jest.advanceTimersByTime(60) 392 | return '' 393 | }) 394 | setTimeout(() => c.abort(), 50) 395 | await expect(fetch('/', { signal: c.signal })).rejects.toThrow('Aborted!') 396 | }) 397 | }) 398 | ``` 399 | 400 | 401 | ### Mocking a redirected response 402 | Set the counter option >= 1 in the response init object to mock a redirected response https://developer.mozilla.org/en-US/docs/Web/API/Response/redirected. Note, this will only work in Node.js as it's a feature of node fetch's response class https://github.com/node-fetch/node-fetch/blob/master/src/response.js#L39. 403 | 404 | ```js 405 | fetchMock.mockResponse("
", { 406 | counter: 1, 407 | status: 200, 408 | statusText: "ok", 409 | }); 410 | ``` 411 | 412 | ### Mocking multiple fetches with different responses 413 | 414 | In this next example, the store does not yet have a token, so we make a request to get an access token first. This means that we need to mock two different responses, one for each of the fetches. Here we can use `fetch.mockResponseOnce` or `fetch.once` to mock the response only once and call it twice. Because we return the mocked function, we can chain this jQuery style. It internally uses Jest's `mockImplementationOnce`. You can read more about it on the [Jest documentation](https://facebook.github.io/jest/docs/mock-functions.html#content) 415 | 416 | ```js 417 | import configureMockStore from 'redux-mock-store' 418 | import thunk from 'redux-thunk' 419 | 420 | const middlewares = [thunk] 421 | const mockStore = configureMockStore(middlewares) 422 | 423 | import { getAnimeDetails } from './animeDetails' 424 | 425 | describe('Anime details action creators', () => { 426 | it('dispatches requests for an access token before requesting for animeDetails', () => { 427 | fetch 428 | .once(JSON.stringify({ access_token: '12345' })) 429 | .once(JSON.stringify({ name: 'naruto' })) 430 | 431 | const expectedActions = [ 432 | { type: 'SET_ACCESS_TOKEN', token: { access_token: '12345' } }, 433 | { type: 'SET_ANIME_DETAILS', animeDetails: { name: 'naruto' } } 434 | ] 435 | const store = mockStore({ config: { token: null } }) 436 | 437 | return ( 438 | store 439 | .dispatch(getAnimeDetails('21049')) 440 | //getAnimeDetails contains the 2 fetch calls 441 | .then(() => { 442 | // return of async actions 443 | expect(store.getActions()).toEqual(expectedActions) 444 | }) 445 | ) 446 | }) 447 | }) 448 | ``` 449 | 450 | ### Mocking multiple fetches with `fetch.mockResponses` 451 | 452 | `fetch.mockResponses` takes as many arguments as you give it, all of which are arrays representing each Response Object. It will then call the `mockImplementationOnce` for each response object you give it. This reduces the amount of boilerplate code you need to write. An alternative is to use `.once` and chain it multiple times if you don't like wrapping each response arguments in a tuple/array. 453 | 454 | In this example our actionCreator calls `fetch` 4 times, once for each season of the year and then concatenates the results into one final array. You'd have to write `fetch.mockResponseOnce` 4 times to achieve the same thing: 455 | 456 | ```js 457 | describe('getYear action creator', () => { 458 | it('dispatches the correct actions on successful getSeason fetch request', () => { 459 | fetch.mockResponses( 460 | [ 461 | JSON.stringify([{ name: 'naruto', average_score: 79 }]), 462 | { status: 200 } 463 | ], 464 | [ 465 | JSON.stringify([{ name: 'bleach', average_score: 68 }]), 466 | { status: 200 } 467 | ], 468 | [ 469 | JSON.stringify([{ name: 'one piece', average_score: 80 }]), 470 | { status: 200 } 471 | ], 472 | [ 473 | JSON.stringify([{ name: 'shingeki', average_score: 91 }]), 474 | { status: 200 } 475 | ] 476 | ) 477 | 478 | const expectedActions = [ 479 | { 480 | type: 'FETCH_ANIMELIST_REQUEST' 481 | }, 482 | { 483 | type: 'SET_YEAR', 484 | payload: { 485 | animes: [ 486 | { name: 'naruto', average_score: 7.9 }, 487 | { name: 'bleach', average_score: 6.8 }, 488 | { name: 'one piece', average_score: 8 }, 489 | { name: 'shingeki', average_score: 9.1 } 490 | ], 491 | year: 2016 492 | } 493 | }, 494 | { 495 | type: 'FETCH_ANIMELIST_COMPLETE' 496 | } 497 | ] 498 | const store = mockStore({ 499 | config: { 500 | token: { access_token: 'wtw45CmyEuh4P621IDVxWkgVr5QwTg3wXEc4Z7Cv' } 501 | }, 502 | years: [] 503 | }) 504 | 505 | return ( 506 | store 507 | .dispatch(getYear(2016)) 508 | //This calls fetch 4 times, once for each season 509 | .then(() => { 510 | // return of async actions 511 | expect(store.getActions()).toEqual(expectedActions) 512 | }) 513 | ) 514 | }) 515 | }) 516 | ``` 517 | 518 | ### Reset mocks between tests with `fetch.resetMocks` 519 | 520 | `fetch.resetMocks` resets the `fetch` mock to give fresh mock data in between tests. It only resets the `fetch` calls as to not disturb any other mocked functionality. 521 | 522 | ```js 523 | describe('getYear action creator', () => { 524 | beforeEach(() => { 525 | fetch.resetMocks(); 526 | }); 527 | it('dispatches the correct actions on successful getSeason fetch request', () => { 528 | 529 | fetch.mockResponses( 530 | [ 531 | JSON.stringify([ {name: 'naruto', average_score: 79} ]), { status: 200} 532 | ], 533 | [ 534 | JSON.stringify([ {name: 'bleach', average_score: 68} ]), { status: 200} 535 | ] 536 | ) 537 | 538 | const expectedActions = [ 539 | { 540 | type: 'FETCH_ANIMELIST_REQUEST' 541 | }, 542 | { 543 | type: 'SET_YEAR', 544 | payload: { 545 | animes: [ 546 | {name: 'naruto', average_score: 7.9}, 547 | {name: 'bleach', average_score: 6.8} 548 | ], 549 | year: 2016, 550 | } 551 | }, 552 | { 553 | type: 'FETCH_ANIMELIST_COMPLETE' 554 | } 555 | ] 556 | const store = mockStore({ 557 | config: { token: { access_token: 'wtw45CmyEuh4P621IDVxWkgVr5QwTg3wXEc4Z7Cv' }}, 558 | years: [] 559 | }) 560 | 561 | return store.dispatch(getYear(2016)) 562 | //This calls fetch 2 times, once for each season 563 | .then(() => { // return of async actions 564 | expect(store.getActions()).toEqual(expectedActions) 565 | }) 566 | }); 567 | it('dispatches the correct actions on successful getSeason fetch request', () => { 568 | 569 | fetch.mockResponses( 570 | [ 571 | JSON.stringify([ {name: 'bleach', average_score: 68} ]), { status: 200} 572 | ], 573 | [ 574 | JSON.stringify([ {name: 'one piece', average_score: 80} ]), { status: 200} 575 | ], 576 | [ 577 | JSON.stringify([ {name: 'shingeki', average_score: 91} ]), { status: 200} 578 | ] 579 | ) 580 | 581 | const expectedActions = [ 582 | { 583 | type: 'FETCH_ANIMELIST_REQUEST' 584 | }, 585 | { 586 | type: 'SET_YEAR', 587 | payload: { 588 | animes: [ 589 | {name: 'bleach', average_score: 6.8}, 590 | {name: 'one piece', average_score: 8}, 591 | {name: 'shingeki', average_score: 9.1} 592 | ], 593 | year: 2016, 594 | } 595 | }, 596 | { 597 | type: 'FETCH_ANIMELIST_COMPLETE' 598 | } 599 | ] 600 | const store = mockStore({ 601 | config: { token: { access_token: 'wtw45CmyEuh4P621IDVxWkgVr5QwTg3wXEc4Z7Cv' }}, 602 | years: [] 603 | }) 604 | 605 | return store.dispatch(getYear(2016)) 606 | //This calls fetch 3 times, once for each season 607 | .then(() => { // return of async actions 608 | expect(store.getActions()).toEqual(expectedActions) 609 | }) 610 | , 611 | 612 | }) 613 | }) 614 | ``` 615 | 616 | ### Using `fetch.mock` to inspect the mock state of each fetch call 617 | 618 | `fetch.mock` by default uses [Jest's mocking functions](https://facebook.github.io/jest/docs/en/mock-functions.html#mock-property). Therefore you can make assertions on the mock state. In this example we have an arbitrary function that makes a different fetch request based on the argument you pass to it. In our test, we run Jest's `beforeEach()` and make sure to reset our mock before each `it()` block as we will make assertions on the arguments we are passing to `fetch()`. The most uses property is the `fetch.mock.calls` array. It can give you information on each call, and their arguments which you can use for your `expect()` calls. Jest also comes with some nice aliases for the most used ones. 619 | 620 | ```js 621 | // api.js 622 | 623 | import 'cross-fetch' 624 | 625 | export function APIRequest(who) { 626 | if (who === 'facebook') { 627 | return fetch('https://facebook.com') 628 | } else if (who === 'twitter') { 629 | return fetch('https://twitter.com') 630 | } else { 631 | return fetch('https://google.com') 632 | } 633 | } 634 | ``` 635 | 636 | ```js 637 | // api.test.js 638 | import { APIRequest } from './api' 639 | 640 | describe('testing api', () => { 641 | beforeEach(() => { 642 | fetch.resetMocks() 643 | }) 644 | 645 | it('calls google by default', () => { 646 | fetch.mockResponse(JSON.stringify({ secret_data: '12345' })) 647 | APIRequest() 648 | 649 | expect(fetch.mock.calls.length).toEqual(1) 650 | expect(fetch.mock.calls[0][0]).toEqual('https://google.com') 651 | }) 652 | 653 | it('calls facebook', () => { 654 | fetch.mockResponse(JSON.stringify({ secret_data: '12345' })) 655 | APIRequest('facebook') 656 | 657 | expect(fetch.mock.calls.length).toEqual(2) 658 | expect(fetch.mock.calls[0][0]).toEqual( 659 | 'https://facebook.com/someOtherResource' 660 | ) 661 | expect(fetch.mock.calls[1][0]).toEqual('https://facebook.com') 662 | }) 663 | 664 | it('calls twitter', () => { 665 | fetch.mockResponse(JSON.stringify({ secret_data: '12345' })) 666 | APIRequest('twitter') 667 | 668 | expect(fetch).toBeCalled() // alias for expect(fetch.mock.calls.length).toEqual(1); 669 | expect(fetch).toBeCalledWith('https://twitter.com') // alias for expect(fetch.mock.calls[0][0]).toEqual(); 670 | }) 671 | }) 672 | ``` 673 | 674 | ### Using functions to mock slow servers 675 | 676 | By default you will want to have your fetch mock return immediately. However if you have some custom logic that needs to tests for slower servers, you can do this by passing it a function and returning a promise when your function resolves 677 | 678 | ```js 679 | // api.test.js 680 | import { request } from './api' 681 | 682 | describe('testing timeouts', () => { 683 | it('resolves with function and timeout', async () => { 684 | fetch.mockResponseOnce( 685 | () => 686 | new Promise(resolve => setTimeout(() => resolve({ body: 'ok' }), 100)) 687 | ) 688 | try { 689 | const response = await request() 690 | expect(response).toEqual('ok') 691 | } catch (e) { 692 | throw e 693 | } 694 | }) 695 | }) 696 | ``` 697 | 698 | ### Conditional Mocking 699 | 700 | In some test scenarios, you may want to temporarily disable (or enable) mocking for all requests or the next (or a certain number of) request(s). 701 | You may want to only mock fetch requests to some URLs that match a given request path while in others you may want to mock 702 | all requests except those matching a given request path. You may even want to conditionally mock based on request headers. 703 | 704 | The conditional mock functions cause `jest-fetch-mock` to pass fetches through to the concrete fetch implementation conditionally. 705 | Calling `fetch.dontMock`, `fetch.doMock`, `fetch.doMockIf` or `fetch.dontMockIf` overrides the default behavior 706 | of mocking/not mocking all requests. `fetch.dontMockOnce`, `fetch.doMockOnce`, `fetch.doMockOnceIf` and `fetch.dontMockOnceIf` only overrides the behavior 707 | for the next call to `fetch`, then returns to the default behavior (either mocking all requests or mocking the requests based on the last call to 708 | `fetch.dontMock`, `fetch.doMock`, `fetch.doMockIf` and `fetch.dontMockIf`). 709 | 710 | Calling `fetch.resetMocks()` will return to the default behavior of mocking all fetches with a text response of empty string. 711 | 712 | - `fetch.dontMock()` - Change the default behavior to not mock any fetches until `fetch.resetMocks()` or `fetch.doMock()` is called 713 | - `fetch.doMock(bodyOrFunction?, responseInit?)` - Reverses `fetch.dontMock()`. This is the default state after `fetch.resetMocks()` 714 | - `fetch.dontMockOnce()` - For the next fetch, do not mock then return to the default behavior for subsequent fetches. Can be chained. 715 | - `fetch.doMockOnce(bodyOrFunction?, responseInit?)` or `fetch.mockOnce` - For the next fetch, mock the response then return to the default behavior for subsequent fetches. Can be chained. 716 | - `fetch.doMockIf(urlOrPredicate, bodyOrFunction?, responseInit?):fetch` or `fetch.mockIf` - causes all fetches to be not be mocked unless they match the given string/RegExp/predicate 717 | (i.e. "only mock 'fetch' if the request is for the given URL otherwise, use the real fetch implementation") 718 | - `fetch.dontMockIf(urlOrPredicate, bodyOrFunction?, responseInit?):fetch` - causes all fetches to be mocked unless they match the given string/RegExp/predicate 719 | (i.e. "don't mock 'fetch' if the request is for the given URL, otherwise mock the request") 720 | - `fetch.doMockOnceIf(urlOrPredicate, bodyOrFunction?, responseInit?):fetch` or `fetch.mockOnceIf` - causes the next fetch to be mocked if it matches the given string/RegExp/predicate. Can be chained. 721 | (i.e. "only mock 'fetch' if the next request is for the given URL otherwise, use the default behavior") 722 | - `fetch.dontMockOnceIf(urlOrPredicate):fetch` - causes the next fetch to be not be mocked if it matches the given string/RegExp/predicate. Can be chained. 723 | (i.e. "don't mock 'fetch' if the next request is for the given URL, otherwise use the default behavior") 724 | - `fetch.isMocking(input, init):boolean` - test utility function to see if the given url/request would be mocked. 725 | This is not a read only operation and any "MockOnce" will evaluate (and return to the default behavior) 726 | 727 | For convenience, all the conditional mocking functions also accept optional parameters after the 1st parameter that call 728 | `mockResponse` or `mockResponseOnce` respectively. This allows you to conditionally mock a response in a single call. 729 | 730 | #### Conditional Mocking examples 731 | 732 | ```js 733 | 734 | describe('conditional mocking', () => { 735 | const realResponse = 'REAL FETCH RESPONSE' 736 | const mockedDefaultResponse = 'MOCKED DEFAULT RESPONSE' 737 | const testUrl = defaultRequestUri 738 | let crossFetchSpy 739 | beforeEach(() => { 740 | fetch.resetMocks() 741 | fetch.mockResponse(mockedDefaultResponse) 742 | crossFetchSpy = jest 743 | .spyOn(require('cross-fetch'), 'fetch') 744 | .mockImplementation(async () => 745 | Promise.resolve(new Response(realResponse)) 746 | ) 747 | }) 748 | 749 | afterEach(() => { 750 | crossFetchSpy.mockRestore() 751 | }) 752 | 753 | const expectMocked = async (uri, response = mockedDefaultResponse) => { 754 | return expect(request(uri)).resolves.toEqual(response) 755 | } 756 | const expectUnmocked = async uri => { 757 | return expect(request(uri)).resolves.toEqual(realResponse) 758 | } 759 | 760 | describe('once', () => { 761 | it('default', async () => { 762 | const otherResponse = 'other response' 763 | fetch.once(otherResponse) 764 | await expectMocked(defaultRequestUri, otherResponse) 765 | await expectMocked() 766 | }) 767 | it('dont mock once then mock twice', async () => { 768 | const otherResponse = 'other response' 769 | fetch 770 | .dontMockOnce() 771 | .once(otherResponse) 772 | .once(otherResponse) 773 | 774 | await expectUnmocked() 775 | await expectMocked(defaultRequestUri, otherResponse) 776 | await expectMocked() 777 | }) 778 | }) 779 | 780 | describe('doMockIf', () => { 781 | it("doesn't mock normally", async () => { 782 | fetch.doMockIf('http://foo') 783 | await expectUnmocked() 784 | await expectUnmocked() 785 | }) 786 | it('mocks when matches string', async () => { 787 | fetch.doMockIf(testUrl) 788 | await expectMocked() 789 | await expectMocked() 790 | }) 791 | it('mocks when matches regex', async () => { 792 | fetch.doMockIf(new RegExp(testUrl)) 793 | await expectMocked() 794 | await expectMocked() 795 | }) 796 | it('mocks when matches predicate', async () => { 797 | fetch.doMockIf(input => input.url === testUrl) 798 | await expectMocked() 799 | await expectMocked() 800 | }) 801 | }) 802 | 803 | describe('dontMockIf', () => { 804 | it('mocks normally', async () => { 805 | fetch.dontMockIf('http://foo') 806 | await expectMocked() 807 | await expectMocked() 808 | }) 809 | it('doesnt mock when matches string', async () => { 810 | fetch.dontMockIf(testUrl) 811 | await expectUnmocked() 812 | await expectUnmocked() 813 | }) 814 | it('doesnt mock when matches regex', async () => { 815 | fetch.dontMockIf(new RegExp(testUrl)) 816 | await expectUnmocked() 817 | await expectUnmocked() 818 | }) 819 | it('doesnt mock when matches predicate', async () => { 820 | fetch.dontMockIf(input => input.url === testUrl) 821 | await expectUnmocked() 822 | await expectUnmocked() 823 | }) 824 | }) 825 | 826 | describe('doMockOnceIf (default mocked)', () => { 827 | it("doesn't mock normally", async () => { 828 | fetch.doMockOnceIf('http://foo') 829 | await expectUnmocked() 830 | await expectMocked() 831 | }) 832 | it('mocks when matches string', async () => { 833 | fetch.doMockOnceIf(testUrl) 834 | await expectMocked() 835 | await expectMocked() 836 | }) 837 | it('mocks when matches regex', async () => { 838 | fetch.doMockOnceIf(new RegExp(testUrl)) 839 | await expectMocked() 840 | await expectMocked() 841 | }) 842 | it('mocks when matches predicate', async () => { 843 | fetch.doMockOnceIf(input => input.url === testUrl) 844 | await expectMocked() 845 | await expectMocked() 846 | }) 847 | }) 848 | 849 | describe('dontMockOnceIf (default mocked)', () => { 850 | it('mocks normally', async () => { 851 | fetch.dontMockOnceIf('http://foo') 852 | await expectMocked() 853 | await expectMocked() 854 | }) 855 | it('doesnt mock when matches string', async () => { 856 | fetch.dontMockOnceIf(testUrl) 857 | await expectUnmocked() 858 | await expectMocked() 859 | }) 860 | it('doesnt mock when matches regex', async () => { 861 | fetch.dontMockOnceIf(new RegExp(testUrl)) 862 | await expectUnmocked() 863 | await expectMocked() 864 | }) 865 | it('doesnt mock when matches predicate', async () => { 866 | fetch.dontMockOnceIf(input => input.url === testUrl) 867 | await expectUnmocked() 868 | await expectMocked() 869 | }) 870 | }) 871 | 872 | describe('doMockOnceIf (default unmocked)', () => { 873 | beforeEach(() => { 874 | fetch.dontMock() 875 | }) 876 | it("doesn't mock normally", async () => { 877 | fetch.doMockOnceIf('http://foo') 878 | await expectUnmocked() 879 | await expectUnmocked() 880 | }) 881 | it('mocks when matches string', async () => { 882 | fetch.doMockOnceIf(testUrl) 883 | await expectMocked() 884 | await expectUnmocked() 885 | }) 886 | it('mocks when matches regex', async () => { 887 | fetch.doMockOnceIf(new RegExp(testUrl)) 888 | await expectMocked() 889 | await expectUnmocked() 890 | }) 891 | it('mocks when matches predicate', async () => { 892 | fetch.doMockOnceIf(input => input.url === testUrl) 893 | await expectMocked() 894 | await expectUnmocked() 895 | }) 896 | }) 897 | 898 | describe('dontMockOnceIf (default unmocked)', () => { 899 | beforeEach(() => { 900 | fetch.dontMock() 901 | }) 902 | it('mocks normally', async () => { 903 | fetch.dontMockOnceIf('http://foo') 904 | await expectMocked() 905 | await expectUnmocked() 906 | }) 907 | it('doesnt mock when matches string', async () => { 908 | fetch.dontMockOnceIf(testUrl) 909 | await expectUnmocked() 910 | await expectUnmocked() 911 | }) 912 | it('doesnt mock when matches regex', async () => { 913 | fetch.dontMockOnceIf(new RegExp(testUrl)) 914 | await expectUnmocked() 915 | await expectUnmocked() 916 | }) 917 | it('doesnt mock when matches predicate', async () => { 918 | fetch.dontMockOnceIf(input => input.url === testUrl) 919 | await expectUnmocked() 920 | await expectUnmocked() 921 | }) 922 | }) 923 | 924 | describe('dont/do mock', () => { 925 | test('dontMock', async () => { 926 | fetch.dontMock() 927 | await expectUnmocked() 928 | await expectUnmocked() 929 | }) 930 | test('dontMockOnce', async () => { 931 | fetch.dontMockOnce() 932 | await expectUnmocked() 933 | await expectMocked() 934 | }) 935 | test('doMock', async () => { 936 | fetch.dontMock() 937 | fetch.doMock() 938 | await expectMocked() 939 | await expectMocked() 940 | }) 941 | test('doMockOnce', async () => { 942 | fetch.dontMock() 943 | fetch.doMockOnce() 944 | await expectMocked() 945 | await expectUnmocked() 946 | }) 947 | }) 948 | 949 | ``` 950 | 951 | ```js 952 | const expectMocked = async (uri, response = mockedDefaultResponse) => { 953 | return expect(request(uri)).resolves.toEqual(response) 954 | } 955 | const expectUnmocked = async uri => { 956 | return expect(request(uri)).resolves.toEqual(realResponse) 957 | } 958 | 959 | describe('conditional mocking complex', () => { 960 | const realResponse = 'REAL FETCH RESPONSE' 961 | const mockedDefaultResponse = 'MOCKED DEFAULT RESPONSE' 962 | const testUrl = defaultRequestUri 963 | let crossFetchSpy 964 | beforeEach(() => { 965 | fetch.resetMocks() 966 | fetch.mockResponse(mockedDefaultResponse) 967 | crossFetchSpy = jest 968 | .spyOn(require('cross-fetch'), 'fetch') 969 | .mockImplementation(async () => 970 | Promise.resolve(new Response(realResponse)) 971 | ) 972 | }) 973 | 974 | afterEach(() => { 975 | crossFetchSpy.mockRestore() 976 | }) 977 | 978 | describe('complex example', () => { 979 | const alternativeUrl = 'http://bar' 980 | const alternativeBody = 'ALTERNATIVE RESPONSE' 981 | beforeEach(() => { 982 | fetch 983 | // .mockResponse(mockedDefaultResponse) // set above - here for clarity 984 | .mockResponseOnce('1') // 1 985 | .mockResponseOnce('2') // 2 986 | .mockResponseOnce(async uri => 987 | uri === alternativeUrl ? alternativeBody : '3' 988 | ) // 3 989 | .mockResponseOnce('4') // 4 990 | .mockResponseOnce('5') // 5 991 | .mockResponseOnce(async uri => 992 | uri === alternativeUrl ? alternativeBody : mockedDefaultResponse 993 | ) // 6 994 | }) 995 | 996 | describe('default (`doMock`)', () => { 997 | beforeEach(() => { 998 | fetch 999 | // .doMock() // the default - here for clarify 1000 | .dontMockOnceIf(alternativeUrl) 1001 | .doMockOnceIf(alternativeUrl) 1002 | .doMockOnce() 1003 | .dontMockOnce() 1004 | }) 1005 | 1006 | test('defaultRequestUri', async () => { 1007 | await expectMocked(defaultRequestUri, '1') // 1 1008 | await expectUnmocked(defaultRequestUri) // 2 1009 | await expectMocked(defaultRequestUri, '3') // 3 1010 | await expectUnmocked(defaultRequestUri) // 4 1011 | // after .once('..') 1012 | await expectMocked(defaultRequestUri, '5') // 5 1013 | await expectMocked(defaultRequestUri, mockedDefaultResponse) // 6 1014 | // default 'isMocked' (not 'Once') 1015 | await expectMocked(defaultRequestUri, mockedDefaultResponse) // 7 1016 | }) 1017 | 1018 | test('alternativeUrl', async () => { 1019 | await expectUnmocked(alternativeUrl) // 1 1020 | await expectMocked(alternativeUrl, '2') // 2 1021 | await expectMocked(alternativeUrl, alternativeBody) // 3 1022 | await expectUnmocked(alternativeUrl) // 4 1023 | // after .once('..') 1024 | await expectMocked(alternativeUrl, '5') // 5 1025 | await expectMocked(alternativeUrl, alternativeBody) // 6 1026 | // default 'isMocked' (not 'Once') 1027 | await expectMocked(alternativeUrl, mockedDefaultResponse) // 7 1028 | }) 1029 | }) 1030 | 1031 | describe('dontMock', () => { 1032 | beforeEach(() => { 1033 | fetch 1034 | .dontMock() 1035 | .dontMockOnceIf(alternativeUrl) 1036 | .doMockOnceIf(alternativeUrl) 1037 | .doMockOnce() 1038 | .dontMockOnce() 1039 | }) 1040 | 1041 | test('defaultRequestUri', async () => { 1042 | await expectMocked(defaultRequestUri, '1') // 1 1043 | await expectUnmocked(defaultRequestUri) // 2 1044 | await expectMocked(defaultRequestUri, '3') // 3 1045 | await expectUnmocked(defaultRequestUri) // 4 1046 | // after .once('..') 1047 | await expectUnmocked(defaultRequestUri) // 5 1048 | await expectUnmocked(defaultRequestUri) // 6 1049 | // default 'isMocked' (not 'Once') 1050 | await expectUnmocked(defaultRequestUri) // 7 1051 | }) 1052 | 1053 | test('alternativeUrl', async () => { 1054 | await expectUnmocked(alternativeUrl) // 1 1055 | await expectMocked(alternativeUrl, '2') // 2 1056 | await expectMocked(alternativeUrl, alternativeBody) // 3 1057 | await expectUnmocked(alternativeUrl) // 4 1058 | // after .once('..') 1059 | await expectUnmocked(alternativeUrl) // 5 1060 | await expectUnmocked(alternativeUrl) // 6 1061 | // default 'isMocked' (not 'Once') 1062 | await expectUnmocked(alternativeUrl) // 7 1063 | }) 1064 | }) 1065 | 1066 | describe('doMockIf(alternativeUrl)', () => { 1067 | beforeEach(() => { 1068 | fetch 1069 | .doMockIf(alternativeUrl) 1070 | .dontMockOnceIf(alternativeUrl) 1071 | .doMockOnceIf(alternativeUrl) 1072 | .doMockOnce() 1073 | .dontMockOnce() 1074 | }) 1075 | 1076 | test('defaultRequestUri', async () => { 1077 | await expectMocked(defaultRequestUri, '1') // 1 1078 | await expectUnmocked(defaultRequestUri) // 2 1079 | await expectMocked(defaultRequestUri, '3') // 3 1080 | await expectUnmocked(defaultRequestUri) // 4 1081 | // after .once('..') 1082 | await expectUnmocked(defaultRequestUri) // 5 1083 | await expectUnmocked(defaultRequestUri) // 6 1084 | // default 'isMocked' (not 'Once') 1085 | await expectUnmocked(defaultRequestUri) // 7 1086 | }) 1087 | 1088 | test('alternativeUrl', async () => { 1089 | await expectUnmocked(alternativeUrl) // 1 1090 | await expectMocked(alternativeUrl, '2') // 2 1091 | await expectMocked(alternativeUrl, alternativeBody) // 3 1092 | await expectUnmocked(alternativeUrl) // 4 1093 | // after .once('..') 1094 | await expectMocked(alternativeUrl, '5') // 5 1095 | await expectMocked(alternativeUrl, alternativeBody) // 6 1096 | // default 'isMocked' (not 'Once') 1097 | await expectMocked(alternativeUrl, mockedDefaultResponse) // 7 1098 | }) 1099 | }) 1100 | 1101 | describe('dontMockIf(alternativeUrl)', () => { 1102 | beforeEach(() => { 1103 | fetch 1104 | .dontMockIf(alternativeUrl) 1105 | .dontMockOnceIf(alternativeUrl) 1106 | .doMockOnceIf(alternativeUrl) 1107 | .doMockOnce() 1108 | .dontMockOnce() 1109 | }) 1110 | 1111 | test('defaultRequestUri', async () => { 1112 | await expectMocked(defaultRequestUri, '1') // 1 1113 | await expectUnmocked(defaultRequestUri) // 2 1114 | await expectMocked(defaultRequestUri, '3') // 3 1115 | await expectUnmocked(defaultRequestUri) // 4 1116 | // after .once('..') 1117 | await expectMocked(defaultRequestUri, '5') // 5 1118 | await expectMocked(defaultRequestUri, mockedDefaultResponse) // 6 1119 | // default 'isMocked' (not 'Once') 1120 | await expectMocked(defaultRequestUri, mockedDefaultResponse) // 7 1121 | }) 1122 | 1123 | test('alternativeUrl', async () => { 1124 | await expectUnmocked(alternativeUrl) // 1 1125 | await expectMocked(alternativeUrl, '2') // 2 1126 | await expectMocked(alternativeUrl, alternativeBody) // 3 1127 | await expectUnmocked(alternativeUrl) // 4 1128 | // after .once('..') 1129 | await expectUnmocked(alternativeUrl) // 5 1130 | await expectUnmocked(alternativeUrl) // 6 1131 | // default 'isMocked' (not 'Once') 1132 | await expectUnmocked(alternativeUrl) // 7 1133 | }) 1134 | }) 1135 | }) 1136 | }) 1137 | ``` 1138 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-fetch-mock", 3 | "version": "3.1.0", 4 | "description": "fetch mock for jest", 5 | "main": "src/index.js", 6 | "types": "types", 7 | "scripts": { 8 | "test": "jest && yarn tsc && yarn lint", 9 | "lint": "eslint .", 10 | "tsc": "tsc" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/jefflau/jest-fetch-mock.git" 15 | }, 16 | "keywords": [ 17 | "jest", 18 | "mock", 19 | "fetch" 20 | ], 21 | "author": "Jeff Lau (http://jefflau.net/)", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/jefflau/jest-fetch-mock/issues" 25 | }, 26 | "homepage": "https://github.com/jefflau/jest-fetch-mock#readme", 27 | "dependencies": { 28 | "cross-fetch": "^3.1.8", 29 | "domexception": "^4.0.0", 30 | "promise-polyfill": "^8.3.0" 31 | }, 32 | "devDependencies": { 33 | "@types/jest": "^29.5.12", 34 | "@types/node": "^20.11.27", 35 | "@typescript-eslint/eslint-plugin": "^7.2.0", 36 | "@typescript-eslint/parser": "^7.2.0", 37 | "eslint": "^8.57.0", 38 | "jest": "^29.7.0", 39 | "prettier": "^3.2.5", 40 | "regenerator-runtime": "^0.14.1", 41 | "typescript": "^5.4.2" 42 | }, 43 | "prettier": { 44 | "semi": false, 45 | "arrowParens": "always", 46 | "editor.formatOnSave": true, 47 | "singleQuote": true, 48 | "trailingComma": "es5", 49 | "overrides": [ 50 | { 51 | "files": "**/*.ts", 52 | "options": { 53 | "semi": true, 54 | "tabWidth": 4, 55 | "singleQuote": false, 56 | "printWidth": 125 57 | } 58 | } 59 | ] 60 | }, 61 | "jest": { 62 | "automock": false, 63 | "testPathIgnorePatterns": [ 64 | "types" 65 | ], 66 | "setupFiles": [ 67 | "./setupJest.js" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /setupJest.js: -------------------------------------------------------------------------------- 1 | require('jest-fetch-mock').enableMocks() 2 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const crossFetch = require('cross-fetch') 2 | global.fetch = crossFetch 3 | global.Response = crossFetch.Response 4 | global.Headers = crossFetch.Headers 5 | global.Request = crossFetch.Request 6 | 7 | if (typeof Promise === 'undefined') { 8 | Promise = require('promise-polyfill') 9 | } else if (!('finally' in Promise) || typeof Promise.finally === 'undefined') { 10 | Promise.finally = require('promise-polyfill').finally 11 | } 12 | 13 | if (typeof DOMException === 'undefined') { 14 | DOMException = require('domexception') 15 | } 16 | 17 | const ActualResponse = Response 18 | 19 | function responseWrapper(body, init) { 20 | if ( 21 | body && 22 | typeof body.constructor === 'function' && 23 | body.constructor.__isFallback 24 | ) { 25 | const response = new ActualResponse(null, init) 26 | response.body = body 27 | 28 | const actualClone = response.clone 29 | response.clone = () => { 30 | const clone = actualClone.call(response) 31 | const [body1, body2] = body.tee() 32 | response.body = body1 33 | clone.body = body2 34 | return clone 35 | } 36 | 37 | return response 38 | } 39 | 40 | return new ActualResponse(body, init) 41 | } 42 | 43 | function responseInit(resp, init) { 44 | if (typeof resp.init === 'object') { 45 | return resp.init 46 | } else { 47 | init = Object.assign({}, init || {}) 48 | for (const field of ['status', 'statusText', 'headers', 'url']) { 49 | if (field in resp) { 50 | init[field] = resp[field] 51 | } 52 | } 53 | return init 54 | } 55 | } 56 | 57 | function requestMatches(urlOrPredicate) { 58 | const predicate = 59 | urlOrPredicate instanceof RegExp 60 | ? (input) => urlOrPredicate.test(input.url) 61 | : typeof urlOrPredicate === 'string' 62 | ? (input) => input.url === urlOrPredicate 63 | : urlOrPredicate 64 | return (input, reqInit) => { 65 | const req = normalizeRequest(input, reqInit) 66 | return [predicate(req), req] 67 | } 68 | } 69 | 70 | function requestNotMatches(urlOrPredicate) { 71 | const matches = requestMatches(urlOrPredicate) 72 | return (input) => { 73 | const result = matches(input) 74 | return [!result[0], result[1]] 75 | } 76 | } 77 | 78 | function staticMatches(value) { 79 | return (input, reqInit) => { 80 | return [value, normalizeRequest(input, reqInit)] 81 | } 82 | } 83 | 84 | const isFn = (unknown) => typeof unknown === 'function' 85 | 86 | const isMocking = jest.fn(staticMatches(true)) 87 | 88 | const abortError = () => 89 | new DOMException('The operation was aborted. ', 'AbortError') 90 | 91 | const abort = () => { 92 | throw abortError() 93 | } 94 | 95 | const abortAsync = () => { 96 | return Promise.reject(abortError()) 97 | } 98 | 99 | const toPromise = (val) => (val instanceof Promise ? val : Promise.resolve(val)) 100 | 101 | const normalizeResponse = (bodyOrFunction, init) => (input, reqInit) => { 102 | const [mocked, request] = isMocking(input, reqInit) 103 | return mocked 104 | ? isFn(bodyOrFunction) 105 | ? toPromise(bodyOrFunction(request)).then((resp) => { 106 | if (request.signal && request.signal.aborted) { 107 | abort() 108 | } 109 | return typeof resp === 'string' 110 | ? responseWrapper(resp, init) 111 | : responseWrapper(resp.body, responseInit(resp, init)) 112 | }) 113 | : new Promise((resolve, reject) => { 114 | if (request.signal && request.signal.aborted) { 115 | reject(abortError()) 116 | return 117 | } 118 | resolve(responseWrapper(bodyOrFunction, init)) 119 | }) 120 | : crossFetch.fetch(input, reqInit) 121 | } 122 | 123 | const normalizeRequest = (input, reqInit) => { 124 | if (input instanceof Request) { 125 | if (input.signal && input.signal.aborted) { 126 | abort() 127 | } 128 | return input 129 | } else if (typeof input === 'string') { 130 | if (reqInit && reqInit.signal && reqInit.signal.aborted) { 131 | abort() 132 | } 133 | return new Request(input, reqInit) 134 | } else if (typeof input.toString === 'function') { 135 | if (reqInit && reqInit.signal && reqInit.signal.aborted) { 136 | abort() 137 | } 138 | return new Request(input.toString(), reqInit) 139 | } else { 140 | throw new TypeError('Unable to parse input as string or Request') 141 | } 142 | } 143 | 144 | const normalizeError = (errorOrFunction) => 145 | isFn(errorOrFunction) 146 | ? errorOrFunction 147 | : () => Promise.reject(errorOrFunction) 148 | 149 | const fetch = jest.fn(normalizeResponse('')) 150 | fetch.Headers = Headers 151 | fetch.Response = responseWrapper 152 | fetch.Request = Request 153 | fetch.mockResponse = (bodyOrFunction, init) => 154 | fetch.mockImplementation(normalizeResponse(bodyOrFunction, init)) 155 | 156 | fetch.mockReject = (errorOrFunction) => 157 | fetch.mockImplementation(normalizeError(errorOrFunction)) 158 | 159 | fetch.mockAbort = () => fetch.mockImplementation(abortAsync) 160 | fetch.mockAbortOnce = () => fetch.mockImplementationOnce(abortAsync) 161 | 162 | const mockResponseOnce = (bodyOrFunction, init) => 163 | fetch.mockImplementationOnce(normalizeResponse(bodyOrFunction, init)) 164 | 165 | fetch.mockResponseOnce = mockResponseOnce 166 | 167 | fetch.once = mockResponseOnce 168 | 169 | fetch.mockRejectOnce = (errorOrFunction) => 170 | fetch.mockImplementationOnce(normalizeError(errorOrFunction)) 171 | 172 | fetch.mockResponses = (...responses) => { 173 | responses.forEach((response) => { 174 | if (Array.isArray(response)) { 175 | const [body, init] = response 176 | fetch.mockImplementationOnce(normalizeResponse(body, init)) 177 | } else { 178 | fetch.mockImplementationOnce(normalizeResponse(response)) 179 | } 180 | }) 181 | return fetch 182 | } 183 | 184 | fetch.isMocking = (req, reqInit) => isMocking(req, reqInit)[0] 185 | 186 | fetch.mockIf = fetch.doMockIf = (urlOrPredicate, bodyOrFunction, init) => { 187 | isMocking.mockImplementation(requestMatches(urlOrPredicate)) 188 | if (bodyOrFunction) { 189 | fetch.mockResponse(bodyOrFunction, init) 190 | } 191 | return fetch 192 | } 193 | 194 | fetch.dontMockIf = (urlOrPredicate, bodyOrFunction, init) => { 195 | isMocking.mockImplementation(requestNotMatches(urlOrPredicate)) 196 | if (bodyOrFunction) { 197 | fetch.mockResponse(bodyOrFunction, init) 198 | } 199 | return fetch 200 | } 201 | 202 | fetch.mockOnceIf = fetch.doMockOnceIf = ( 203 | urlOrPredicate, 204 | bodyOrFunction, 205 | init 206 | ) => { 207 | isMocking.mockImplementationOnce(requestMatches(urlOrPredicate)) 208 | if (bodyOrFunction) { 209 | mockResponseOnce(bodyOrFunction, init) 210 | } 211 | return fetch 212 | } 213 | 214 | fetch.dontMockOnceIf = (urlOrPredicate, bodyOrFunction, init) => { 215 | isMocking.mockImplementationOnce(requestNotMatches(urlOrPredicate)) 216 | if (bodyOrFunction) { 217 | mockResponseOnce(bodyOrFunction, init) 218 | } 219 | return fetch 220 | } 221 | 222 | fetch.dontMock = () => { 223 | isMocking.mockImplementation(staticMatches(false)) 224 | return fetch 225 | } 226 | 227 | fetch.dontMockOnce = () => { 228 | isMocking.mockImplementationOnce(staticMatches(false)) 229 | return fetch 230 | } 231 | 232 | fetch.doMock = (bodyOrFunction, init) => { 233 | isMocking.mockImplementation(staticMatches(true)) 234 | if (bodyOrFunction) { 235 | fetch.mockResponse(bodyOrFunction, init) 236 | } 237 | return fetch 238 | } 239 | 240 | fetch.mockOnce = fetch.doMockOnce = (bodyOrFunction, init) => { 241 | isMocking.mockImplementationOnce(staticMatches(true)) 242 | if (bodyOrFunction) { 243 | mockResponseOnce(bodyOrFunction, init) 244 | } 245 | return fetch 246 | } 247 | 248 | fetch.resetMocks = () => { 249 | fetch.mockReset() 250 | isMocking.mockReset() 251 | 252 | // reset to default implementation with each reset 253 | fetch.mockImplementation(normalizeResponse('')) 254 | fetch.doMock() 255 | fetch.isMocking = (req, reqInit) => isMocking(req, reqInit)[0] 256 | } 257 | 258 | fetch.enableMocks = fetch.enableFetchMocks = () => { 259 | global.fetchMock = global.fetch = fetch 260 | try { 261 | jest.setMock('node-fetch', fetch) 262 | } catch (error) { 263 | //ignore 264 | } 265 | } 266 | 267 | fetch.disableMocks = fetch.disableFetchMocks = () => { 268 | global.fetch = crossFetch 269 | try { 270 | jest.dontMock('node-fetch') 271 | } catch (error) { 272 | //ignore 273 | } 274 | } 275 | 276 | Object.defineProperty(exports, '__esModule', { value: true }) 277 | module.exports = fetch.default = fetch 278 | -------------------------------------------------------------------------------- /tests/api.js: -------------------------------------------------------------------------------- 1 | require('cross-fetch/polyfill') 2 | 3 | async function APIRequest(who) { 4 | if (who === 'facebook') { 5 | const call1 = fetch('https://facebook.com/someOtherResource').then((res) => 6 | res.json() 7 | ) 8 | const call2 = fetch('https://facebook.com').then((res) => res.json()) 9 | return Promise.all([call1, call2]) 10 | } else if (who === 'twitter') { 11 | return fetch('https://twitter.com').then((res) => res.json()) 12 | } else if (who === 'instagram') { 13 | return fetch(new URL('https://instagram.com')).then((res) => res.json()) 14 | } else if (who === 'bing') { 15 | const stringifier = { 16 | toString: () => 'https://bing.com' 17 | } 18 | return fetch(stringifier).then((res) => res.json()) 19 | } else { 20 | return fetch('https://google.com').then((res) => res.json()) 21 | } 22 | } 23 | 24 | function APIRequest2(who) { 25 | if (who === 'google') { 26 | return fetch('https://google.com').then((res) => res.json()) 27 | } else { 28 | return 'no argument provided' 29 | } 30 | } 31 | 32 | const defaultRequestUri = 'https://randomuser.me/api' 33 | 34 | function request(uri = defaultRequestUri) { 35 | return fetch(uri, {}) 36 | .then((response) => { 37 | const contentType = response.headers.get('content-type') 38 | 39 | if (/application\/json/.test(contentType)) { 40 | return response.json() 41 | } 42 | 43 | if (/text\/csv/.test(contentType)) { 44 | return response.blob() 45 | } 46 | 47 | if (!contentType || /^text\/|charset=utf-8$/.test(contentType)) { 48 | return response.text() 49 | } 50 | 51 | return response 52 | }) 53 | .catch((error) => { 54 | const errorData = JSON.parse(error) 55 | throw new Error(errorData.error) 56 | }) 57 | } 58 | 59 | module.exports = { 60 | request, 61 | APIRequest, 62 | APIRequest2, 63 | defaultRequestUri, 64 | } 65 | -------------------------------------------------------------------------------- /tests/node.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | if (typeof DOMException === 'undefined') { 5 | DOMException = require('domexception') 6 | } 7 | 8 | it('rejects with a dom exception', () => { 9 | fetch.mockAbort() 10 | expect(fetch('/')).rejects.toThrow(expect.any(DOMException)) 11 | }) 12 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | const { APIRequest, APIRequest2, defaultRequestUri, request } = require('./api') 2 | 3 | describe('testing mockResponse and alias once', () => { 4 | beforeEach(() => { 5 | fetch.resetMocks() 6 | }) 7 | it('mocks a response', async () => { 8 | fetch.mockResponseOnce( 9 | JSON.stringify({ secret_data: 'abcde' }, { status: 200 }) 10 | ) 11 | 12 | const response = await APIRequest('google') 13 | 14 | expect(response).toEqual({ secret_data: 'abcde' }) 15 | expect(fetch.mock.calls.length).toEqual(1) 16 | expect(fetch.mock.calls[0][0]).toEqual('https://google.com') 17 | }) 18 | 19 | it('mocks a response with chaining', async () => { 20 | fetch 21 | .mockResponseOnce( 22 | JSON.stringify({ secret_data: '12345' }, { status: 200 }) 23 | ) 24 | .mockResponseOnce( 25 | JSON.stringify({ secret_data: '67891' }, { status: 200 }) 26 | ) 27 | 28 | const response = await APIRequest('facebook') 29 | 30 | expect(response).toEqual([ 31 | { secret_data: '12345' }, 32 | { secret_data: '67891' }, 33 | ]) 34 | 35 | expect(fetch.mock.calls.length).toEqual(2) 36 | 37 | expect(fetch.mock.calls[0][0]).toEqual( 38 | 'https://facebook.com/someOtherResource' 39 | ) 40 | expect(fetch.mock.calls[1][0]).toEqual('https://facebook.com') 41 | }) 42 | 43 | it('mocks a response with alias .once', async () => { 44 | fetch.mockResponseOnce( 45 | JSON.stringify({ secret_data: 'abcde' }, { status: 200 }) 46 | ) 47 | 48 | const response = await APIRequest('google') 49 | 50 | expect(response).toEqual({ secret_data: 'abcde' }) 51 | expect(fetch.mock.calls.length).toEqual(1) 52 | expect(fetch.mock.calls[0][0]).toEqual('https://google.com') 53 | }) 54 | 55 | it('mocks a response with chaining with alias .once', async () => { 56 | fetch 57 | .once(JSON.stringify({ secret_data: '12345' }), { status: 200 }) 58 | .once(JSON.stringify({ secret_data: '67891' }), { status: 200 }) 59 | 60 | const response = await APIRequest('facebook') 61 | 62 | expect(response).toEqual([ 63 | { secret_data: '12345' }, 64 | { secret_data: '67891' }, 65 | ]) 66 | 67 | expect(fetch.mock.calls.length).toEqual(2) 68 | 69 | expect(fetch.mock.calls[0][0]).toEqual( 70 | 'https://facebook.com/someOtherResource' 71 | ) 72 | expect(fetch.mock.calls[1][0]).toEqual('https://facebook.com') 73 | }) 74 | 75 | it('supports URLs', async () => { 76 | fetch.mockResponseOnce( 77 | JSON.stringify({ secret_data: 'abcde' }, { status: 200 }) 78 | ) 79 | 80 | const response = await APIRequest('instagram') 81 | 82 | expect(response).toEqual({ secret_data: 'abcde' }) 83 | expect(fetch.mock.calls.length).toEqual(1) 84 | expect(fetch.mock.calls[0][0]).toEqual(new URL('https://instagram.com')) 85 | }); 86 | 87 | it('supports an object with a stringifier', async () => { 88 | fetch.mockResponseOnce( 89 | JSON.stringify({ secret_data: 'abcde' }, { status: 200 }) 90 | ) 91 | 92 | const response = await APIRequest('instagram') 93 | 94 | expect(response).toEqual({ secret_data: 'abcde' }) 95 | expect(fetch.mock.calls.length).toEqual(1) 96 | expect(fetch.mock.calls[0][0]).toEqual(new URL('https://instagram.com')) 97 | }); 98 | }) 99 | 100 | describe('testing mockResponses', () => { 101 | beforeEach(() => { 102 | fetch.resetMocks() 103 | }) 104 | it('mocks multiple responses', async () => { 105 | fetch.mockResponses( 106 | [JSON.stringify({ name: 'naruto', average_score: 79 })], 107 | [JSON.stringify({ name: 'bleach', average_score: 68 })] 108 | ) 109 | 110 | const response = await APIRequest('facebook') 111 | expect(response).toEqual([ 112 | { name: 'naruto', average_score: 79 }, 113 | { name: 'bleach', average_score: 68 }, 114 | ]) 115 | expect(fetch.mock.calls.length).toEqual(2) 116 | 117 | expect(fetch.mock.calls[0][0]).toEqual( 118 | 'https://facebook.com/someOtherResource' 119 | ) 120 | expect(fetch.mock.calls[1][0]).toEqual('https://facebook.com') 121 | }) 122 | }) 123 | 124 | describe('Mocking aborts', () => { 125 | beforeEach(() => { 126 | fetch.resetMocks() 127 | jest.useFakeTimers() 128 | }) 129 | afterEach(() => { 130 | jest.useRealTimers() 131 | }) 132 | 133 | it('rejects with a dom exception', () => { 134 | fetch.mockAbort() 135 | expect(fetch('/')).rejects.toThrow(expect.any(DOMException)) 136 | }) 137 | it('rejects once then mocks with empty response', async () => { 138 | fetch.mockAbortOnce() 139 | await expect(fetch('/')).rejects.toThrow(expect.any(DOMException)) 140 | await expect(request()).resolves.toEqual('') 141 | }) 142 | 143 | it('throws when passed an already aborted abort signal in the request init', () => { 144 | const c = new AbortController() 145 | c.abort() 146 | expect(() => fetch('/', { signal: c.signal })).toThrow( 147 | expect.any(DOMException) 148 | ) 149 | }) 150 | 151 | it('rejects when aborted before resolved', async () => { 152 | const c = new AbortController() 153 | fetch.mockResponse(async () => { 154 | jest.advanceTimersByTime(60) 155 | return '' 156 | }) 157 | setTimeout(() => c.abort(), 50) 158 | await expect(fetch('/', { signal: c.signal })).rejects.toThrow( 159 | expect.any(DOMException) 160 | ) 161 | }) 162 | }) 163 | 164 | describe('Mocking rejects', () => { 165 | beforeEach(() => { 166 | fetch.resetMocks() 167 | }) 168 | 169 | it('mocking rejects', async () => { 170 | fetch.mockRejectOnce('fake error') 171 | return expect(APIRequest2('google')).rejects.toEqual('fake error') 172 | }) 173 | }) 174 | 175 | describe('request', () => { 176 | beforeEach(() => { 177 | fetch.resetMocks() 178 | }) 179 | 180 | it('passes input and init to response function', () => { 181 | const url = 'http://foo.bar/' 182 | const requestInit = { 183 | headers: { 184 | foo: 'bar', 185 | }, 186 | } 187 | const responseInit = { 188 | headers: { 189 | bing: 'dang', 190 | }, 191 | } 192 | const response = 'foobarbang' 193 | fetch.mockResponse((input) => { 194 | expect(input).toHaveProperty('url', url) 195 | expect(input.headers.get('foo')).toEqual('bar') 196 | return Promise.resolve(response) 197 | }, responseInit) 198 | return fetch(url, requestInit).then((resp) => { 199 | expect(resp.headers.get('bing')).toEqual(responseInit.headers.bing) 200 | return expect(resp.text()).resolves.toEqual(response) 201 | }) 202 | }) 203 | 204 | it('returns object when response is json', (done) => { 205 | const mockResponse = { 206 | results: [{ gender: 'neutral' }], 207 | info: { seed: '0123456789123456', results: 1, page: 1, version: '1.2' }, 208 | } 209 | fetch.mockResponseOnce(JSON.stringify(mockResponse), { 210 | headers: { 211 | 'Content-Type': 'application/json', 212 | }, 213 | }) 214 | 215 | request() 216 | .then((response) => { 217 | expect(fetch).toHaveBeenCalledWith('https://randomuser.me/api', {}) 218 | expect(response).toEqual(mockResponse) 219 | done() 220 | }) 221 | .catch(done.fail) 222 | }) 223 | 224 | it('returns text when response is text', (done) => { 225 | fetch.mockResponseOnce('ok') 226 | 227 | request() 228 | .then((response) => { 229 | expect(fetch).toHaveBeenCalledWith('https://randomuser.me/api', {}) 230 | expect(response).toEqual('ok') 231 | done() 232 | }) 233 | .catch(done.fail) 234 | }) 235 | 236 | it('returns blob when response is text/csv', async () => { 237 | const contentType = 'text/csv; charset=utf-8' 238 | fetch.mockResponseOnce('csv data', { 239 | headers: { 240 | 'Content-Type': contentType, 241 | }, 242 | }) 243 | 244 | try { 245 | const response = await request() 246 | expect(response.type).toBe(contentType) 247 | } catch (e) { 248 | console.log(e) 249 | } 250 | 251 | expect(fetch).toHaveBeenCalledWith('https://randomuser.me/api', {}) 252 | }) 253 | 254 | it('rejects with error data', (done) => { 255 | const errorData = { 256 | error: 257 | 'Uh oh, something has gone wrong. Please tweet us @randomapi about the issue. Thank you.', 258 | } 259 | fetch.mockRejectOnce(JSON.stringify(errorData)) 260 | 261 | request() 262 | .then(done.fail) 263 | .catch((error) => { 264 | expect(error.message).toBe(errorData.error) 265 | done() 266 | }) 267 | }) 268 | 269 | it('resolves with function', async () => { 270 | fetch.mockResponseOnce(() => Promise.resolve({ body: 'ok' })) 271 | return expect(request()).resolves.toEqual('ok') 272 | }) 273 | 274 | it('resolves with function and timeout', async () => { 275 | jest.useFakeTimers() 276 | fetch.mockResponseOnce( 277 | () => 278 | new Promise((resolve) => 279 | setTimeout(() => resolve({ body: 'ok' }), 5000) 280 | ) 281 | ) 282 | try { 283 | const req = request() 284 | jest.runAllTimers() 285 | return expect(req).resolves.toEqual('ok') 286 | } finally { 287 | jest.useRealTimers() 288 | } 289 | }) 290 | 291 | it('rejects with function', async () => { 292 | const errorData = { 293 | error: 294 | 'Uh oh, something has gone wrong. Please tweet us @randomapi about the issue. Thank you.', 295 | } 296 | fetch.mockRejectOnce(() => Promise.reject(JSON.stringify(errorData))) 297 | return expect(request()).rejects.toThrow(errorData.error) 298 | }) 299 | 300 | it('rejects with function and timeout', async () => { 301 | const errorData = { 302 | error: 303 | 'Uh oh, something has gone wrong. Please tweet us @randomapi about the issue. Thank you.', 304 | } 305 | fetch.mockRejectOnce( 306 | () => 307 | new Promise((_, reject) => 308 | setTimeout(() => reject(JSON.stringify(errorData))) 309 | ), 310 | 100 311 | ) 312 | try { 313 | await request() 314 | } catch (error) { 315 | expect(error.message).toBe(errorData.error) 316 | } 317 | }) 318 | 319 | it('resolves with function returning object body and init headers', async () => { 320 | fetch.mockResponseOnce( 321 | () => 322 | Promise.resolve({ body: 'ok', init: { headers: { ding: 'dang' } } }), 323 | { headers: { bash: 'bang' } } 324 | ) 325 | 326 | const response = await fetch('https://test.url', {}) 327 | expect(response.headers.get('ding')).toEqual('dang') 328 | expect(response.headers.get('bash')).toBeNull() 329 | return expect(response.text()).resolves.toEqual('ok') 330 | }) 331 | 332 | it('resolves with function returning object body and extends mock params', async () => { 333 | fetch.mockResponseOnce( 334 | () => ({ 335 | body: 'ok', 336 | headers: { ding: 'dang' }, 337 | status: 201, 338 | statusText: 'text', 339 | url: 'http://foo', 340 | }), 341 | { headers: { bash: 'bang' } } 342 | ) 343 | 344 | const response = await fetch('https://bar', {}) 345 | expect(response.headers.get('ding')).toEqual('dang') 346 | expect(response.headers.get('bash')).toBeNull() 347 | expect(response.status).toBe(201) 348 | expect(response.statusText).toEqual('text') 349 | expect(response.url).toEqual('http://foo') 350 | return expect(response.text()).resolves.toEqual('ok') 351 | }) 352 | 353 | it('resolves with mock response headers and function returning string', async () => { 354 | fetch.mockResponseOnce(() => Promise.resolve('ok'), { 355 | headers: { ding: 'dang' }, 356 | }) 357 | return expect( 358 | fetch('https://bar', {}).then((response) => response.headers.get('ding')) 359 | ).resolves.toEqual('dang') 360 | }) 361 | }) 362 | 363 | describe('conditional mocking', () => { 364 | const realResponse = 'REAL FETCH RESPONSE' 365 | const mockedDefaultResponse = 'MOCKED DEFAULT RESPONSE' 366 | const testUrl = defaultRequestUri 367 | let crossFetchSpy 368 | 369 | beforeEach(() => { 370 | fetch.resetMocks() 371 | fetch.mockResponse(mockedDefaultResponse) 372 | crossFetchSpy = jest 373 | .spyOn(require('cross-fetch'), 'fetch') 374 | .mockImplementation(async () => 375 | Promise.resolve(new Response(realResponse)) 376 | ) 377 | }) 378 | 379 | afterEach(() => { 380 | crossFetchSpy.mockRestore() 381 | }) 382 | 383 | const expectMocked = async (uri, response = mockedDefaultResponse) => { 384 | return expect(request(uri)).resolves.toEqual(response) 385 | } 386 | const expectUnmocked = async (uri) => { 387 | return expect(request(uri)).resolves.toEqual(realResponse) 388 | } 389 | 390 | describe('once', () => { 391 | it('default', async () => { 392 | const otherResponse = 'other response' 393 | fetch.once(otherResponse) 394 | await expectMocked(defaultRequestUri, otherResponse) 395 | await expectMocked() 396 | }) 397 | it('dont mock once then mock twice', async () => { 398 | const otherResponse = 'other response' 399 | fetch.dontMockOnce().once(otherResponse).once(otherResponse) 400 | 401 | await expectUnmocked() 402 | await expectMocked(defaultRequestUri, otherResponse) 403 | await expectMocked() 404 | }) 405 | }) 406 | 407 | describe('mockIf', () => { 408 | it("doesn't mock normally", async () => { 409 | fetch.doMockIf('http://foo') 410 | await expectUnmocked() 411 | await expectUnmocked() 412 | }) 413 | it('mocks when matches string', async () => { 414 | fetch.doMockIf(testUrl) 415 | await expectMocked() 416 | await expectMocked() 417 | }) 418 | it('mocks when matches regex', async () => { 419 | fetch.doMockIf(new RegExp(testUrl)) 420 | await expectMocked() 421 | await expectMocked() 422 | }) 423 | it('mocks when matches predicate', async () => { 424 | fetch.doMockIf((input) => input.url === testUrl) 425 | await expectMocked() 426 | await expectMocked() 427 | }) 428 | }) 429 | 430 | describe('dontMockIf', () => { 431 | it('mocks normally', async () => { 432 | fetch.dontMockIf('http://foo') 433 | await expectMocked() 434 | await expectMocked() 435 | }) 436 | it('doesnt mock when matches string', async () => { 437 | fetch.dontMockIf(testUrl) 438 | await expectUnmocked() 439 | await expectUnmocked() 440 | }) 441 | it('doesnt mock when matches regex', async () => { 442 | fetch.dontMockIf(new RegExp(testUrl)) 443 | await expectUnmocked() 444 | await expectUnmocked() 445 | }) 446 | it('doesnt mock when matches predicate', async () => { 447 | fetch.dontMockIf((input) => input.url === testUrl) 448 | await expectUnmocked() 449 | await expectUnmocked() 450 | }) 451 | }) 452 | 453 | describe('mockOnceIf (default mocked)', () => { 454 | it("doesn't mock normally", async () => { 455 | fetch.doMockOnceIf('http://foo') 456 | await expectUnmocked() 457 | await expectMocked() 458 | }) 459 | it('mocks when matches string', async () => { 460 | const response = 'blah' 461 | const response2 = 'blah2' 462 | fetch 463 | .doMockOnceIf('http://foo/', response) 464 | .doMockOnceIf('http://foo2/', response2) 465 | await expectMocked('http://foo/', response) 466 | await expectMocked('http://foo2/', response2) 467 | //await expectMocked('http://foo3', mockedDefaultResponse) 468 | }) 469 | it('mocks when matches regex', async () => { 470 | fetch.doMockOnceIf(new RegExp(testUrl)) 471 | await expectMocked() 472 | await expectMocked() 473 | }) 474 | it('mocks when matches predicate', async () => { 475 | fetch.doMockOnceIf((input) => input.url === testUrl) 476 | await expectMocked() 477 | await expectMocked() 478 | }) 479 | }) 480 | 481 | describe('dontMockOnceIf (default mocked)', () => { 482 | it('mocks normally', async () => { 483 | fetch.dontMockOnceIf('http://foo') 484 | await expectMocked() 485 | await expectMocked() 486 | }) 487 | it('doesnt mock when matches string', async () => { 488 | fetch.dontMockOnceIf(testUrl) 489 | await expectUnmocked() 490 | await expectMocked() 491 | }) 492 | it('doesnt mock when matches regex', async () => { 493 | fetch.dontMockOnceIf(new RegExp(testUrl)) 494 | await expectUnmocked() 495 | await expectMocked() 496 | }) 497 | it('doesnt mock when matches predicate', async () => { 498 | fetch.dontMockOnceIf((input) => input.url === testUrl) 499 | await expectUnmocked() 500 | await expectMocked() 501 | }) 502 | }) 503 | 504 | describe('mockOnceIf (default unmocked)', () => { 505 | beforeEach(() => { 506 | fetch.dontMock() 507 | }) 508 | it("doesn't mock normally", async () => { 509 | fetch.doMockOnceIf('http://foo') 510 | await expectUnmocked() 511 | await expectUnmocked() 512 | }) 513 | it('mocks when matches string', async () => { 514 | fetch.doMockOnceIf(testUrl) 515 | await expectMocked() 516 | await expectUnmocked() 517 | }) 518 | it('mocks when matches regex', async () => { 519 | fetch.doMockOnceIf(new RegExp(testUrl)) 520 | await expectMocked() 521 | await expectUnmocked() 522 | }) 523 | it('mocks when matches predicate', async () => { 524 | fetch.doMockOnceIf((input) => input.url === testUrl) 525 | await expectMocked() 526 | await expectUnmocked() 527 | }) 528 | }) 529 | 530 | describe('dontMockOnceIf (default unmocked)', () => { 531 | beforeEach(() => { 532 | fetch.dontMock() 533 | }) 534 | it('mocks normally', async () => { 535 | fetch.dontMockOnceIf('http://foo') 536 | await expectMocked() 537 | await expectUnmocked() 538 | }) 539 | it('doesnt mock when matches string', async () => { 540 | fetch.dontMockOnceIf(testUrl) 541 | await expectUnmocked() 542 | await expectUnmocked() 543 | }) 544 | it('doesnt mock when matches regex', async () => { 545 | fetch.dontMockOnceIf(new RegExp(testUrl)) 546 | await expectUnmocked() 547 | await expectUnmocked() 548 | }) 549 | it('doesnt mock when matches predicate', async () => { 550 | fetch.dontMockOnceIf((input) => input.url === testUrl) 551 | await expectUnmocked() 552 | await expectUnmocked() 553 | }) 554 | }) 555 | 556 | describe('dont/do mock', () => { 557 | test('dontMock', async () => { 558 | fetch.dontMock() 559 | await expectUnmocked() 560 | await expectUnmocked() 561 | }) 562 | test('dontMockOnce', async () => { 563 | fetch.dontMockOnce() 564 | await expectUnmocked() 565 | await expectMocked() 566 | }) 567 | test('doMock', async () => { 568 | fetch.dontMock() 569 | fetch.doMock() 570 | await expectMocked() 571 | await expectMocked() 572 | }) 573 | test('doMockOnce', async () => { 574 | fetch.dontMock() 575 | fetch.doMockOnce() 576 | await expectMocked() 577 | await expectUnmocked() 578 | }) 579 | }) 580 | 581 | describe('complex example', () => { 582 | const alternativeUrl = 'http://bar/' 583 | const alternativeBody = 'ALTERNATIVE RESPONSE' 584 | beforeEach(() => { 585 | fetch 586 | // .mockResponse(mockedDefaultResponse) // set above - here for clarity 587 | .mockResponseOnce('1') // 1 588 | .mockResponseOnce('2') // 2 589 | .mockResponseOnce(async (request) => 590 | request.url === alternativeUrl ? alternativeBody : '3' 591 | ) // 3 592 | .mockResponseOnce('4') // 4 593 | .mockResponseOnce('5') // 5 594 | .mockResponseOnce(async (request) => 595 | request.url === alternativeUrl 596 | ? alternativeBody 597 | : mockedDefaultResponse 598 | ) // 6 599 | }) 600 | 601 | describe('default (`doMock`)', () => { 602 | beforeEach(() => { 603 | fetch 604 | // .doMock() // the default - here for clarify 605 | .dontMockOnceIf(alternativeUrl) 606 | .doMockOnceIf(alternativeUrl) 607 | .doMockOnce() 608 | .dontMockOnce() 609 | }) 610 | 611 | test('defaultRequestUri', async () => { 612 | await expectMocked(defaultRequestUri, '1') // 1 613 | await expectUnmocked(defaultRequestUri) // 2 614 | await expectMocked(defaultRequestUri, '3') // 3 615 | await expectUnmocked(defaultRequestUri) // 4 616 | // after .once('..') 617 | await expectMocked(defaultRequestUri, '5') // 5 618 | await expectMocked(defaultRequestUri, mockedDefaultResponse) // 6 619 | // default 'isMocked' (not 'Once') 620 | await expectMocked(defaultRequestUri, mockedDefaultResponse) // 7 621 | }) 622 | 623 | test('alternativeUrl', async () => { 624 | await expectUnmocked(alternativeUrl) // 1 625 | await expectMocked(alternativeUrl, '2') // 2 626 | await expectMocked(alternativeUrl, alternativeBody) // 3 627 | await expectUnmocked(alternativeUrl) // 4 628 | // after .once('..') 629 | await expectMocked(alternativeUrl, '5') // 5 630 | await expectMocked(alternativeUrl, alternativeBody) // 6 631 | // default 'isMocked' (not 'Once') 632 | await expectMocked(alternativeUrl, mockedDefaultResponse) // 7 633 | }) 634 | }) 635 | 636 | describe('dontMock', () => { 637 | beforeEach(() => { 638 | fetch 639 | .dontMock() 640 | .dontMockOnceIf(alternativeUrl) 641 | .doMockOnceIf(alternativeUrl) 642 | .doMockOnce() 643 | .dontMockOnce() 644 | }) 645 | 646 | test('defaultRequestUri', async () => { 647 | await expectMocked(defaultRequestUri, '1') // 1 648 | await expectUnmocked(defaultRequestUri) // 2 649 | await expectMocked(defaultRequestUri, '3') // 3 650 | await expectUnmocked(defaultRequestUri) // 4 651 | // after .once('..') 652 | await expectUnmocked(defaultRequestUri) // 5 653 | await expectUnmocked(defaultRequestUri) // 6 654 | // default 'isMocked' (not 'Once') 655 | await expectUnmocked(defaultRequestUri) // 7 656 | }) 657 | 658 | test('alternativeUrl', async () => { 659 | await expectUnmocked(alternativeUrl) // 1 660 | await expectMocked(alternativeUrl, '2') // 2 661 | await expectMocked(alternativeUrl, alternativeBody) // 3 662 | await expectUnmocked(alternativeUrl) // 4 663 | // after .once('..') 664 | await expectUnmocked(alternativeUrl) // 5 665 | await expectUnmocked(alternativeUrl) // 6 666 | // default 'isMocked' (not 'Once') 667 | await expectUnmocked(alternativeUrl) // 7 668 | }) 669 | }) 670 | 671 | describe('doMockIf(alternativeUrl)', () => { 672 | beforeEach(() => { 673 | fetch 674 | .doMockIf(alternativeUrl) 675 | .dontMockOnceIf(alternativeUrl) 676 | .doMockOnceIf(alternativeUrl) 677 | .doMockOnce() 678 | .dontMockOnce() 679 | }) 680 | 681 | test('defaultRequestUri', async () => { 682 | await expectMocked(defaultRequestUri, '1') // 1 683 | await expectUnmocked(defaultRequestUri) // 2 684 | await expectMocked(defaultRequestUri, '3') // 3 685 | await expectUnmocked(defaultRequestUri) // 4 686 | // after .once('..') 687 | await expectUnmocked(defaultRequestUri) // 5 688 | await expectUnmocked(defaultRequestUri) // 6 689 | // default 'isMocked' (not 'Once') 690 | await expectUnmocked(defaultRequestUri) // 7 691 | }) 692 | 693 | test('alternativeUrl', async () => { 694 | await expectUnmocked(alternativeUrl) // 1 695 | await expectMocked(alternativeUrl, '2') // 2 696 | await expectMocked(alternativeUrl, alternativeBody) // 3 697 | await expectUnmocked(alternativeUrl) // 4 698 | // after .once('..') 699 | await expectMocked(alternativeUrl, '5') // 5 700 | await expectMocked(alternativeUrl, alternativeBody) // 6 701 | // default 'isMocked' (not 'Once') 702 | await expectMocked(alternativeUrl, mockedDefaultResponse) // 7 703 | }) 704 | }) 705 | 706 | describe('dontMockIf(alternativeUrl)', () => { 707 | beforeEach(() => { 708 | fetch 709 | .dontMockIf(alternativeUrl) 710 | .dontMockOnceIf(alternativeUrl) 711 | .doMockOnceIf(alternativeUrl) 712 | .doMockOnce() 713 | .dontMockOnce() 714 | }) 715 | 716 | test('defaultRequestUri', async () => { 717 | await expectMocked(defaultRequestUri, '1') // 1 718 | await expectUnmocked(defaultRequestUri) // 2 719 | await expectMocked(defaultRequestUri, '3') // 3 720 | await expectUnmocked(defaultRequestUri) // 4 721 | // after .once('..') 722 | await expectMocked(defaultRequestUri, '5') // 5 723 | await expectMocked(defaultRequestUri, mockedDefaultResponse) // 6 724 | // default 'isMocked' (not 'Once') 725 | await expectMocked(defaultRequestUri, mockedDefaultResponse) // 7 726 | }) 727 | 728 | test('alternativeUrl', async () => { 729 | await expectUnmocked(alternativeUrl) // 1 730 | await expectMocked(alternativeUrl, '2') // 2 731 | await expectMocked(alternativeUrl, alternativeBody) // 3 732 | await expectUnmocked(alternativeUrl) // 4 733 | // after .once('..') 734 | await expectUnmocked(alternativeUrl) // 5 735 | await expectUnmocked(alternativeUrl) // 6 736 | // default 'isMocked' (not 'Once') 737 | await expectUnmocked(alternativeUrl) // 7 738 | }) 739 | }) 740 | }) 741 | }) 742 | 743 | it('enable/disable', () => { 744 | const jestFetchMock = require('jest-fetch-mock') 745 | jestFetchMock.disableMocks() 746 | expect(jest.isMockFunction(fetch)).toBe(false) 747 | jestFetchMock.enableMocks() 748 | expect(jest.isMockFunction(fetch)).toBe(true) 749 | jestFetchMock.disableMocks() 750 | global.fetch = jestFetchMock 751 | }) 752 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./types/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2015", 5 | "baseUrl": ".", 6 | "rootDir": "./types", 7 | "paths": { 8 | "jest-fetch-mock": [ 9 | "./" 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // TypeScript Version: 3.0 2 | /// 3 | import Global = NodeJS.Global; 4 | import "jest"; 5 | 6 | declare global { 7 | const fetchMock: FetchMock; 8 | namespace NodeJS { 9 | interface Global { 10 | fetch: FetchMock; 11 | } 12 | } 13 | } 14 | 15 | export interface GlobalWithFetchMock extends Global { 16 | fetchMock: FetchMock; 17 | fetch: FetchMock; 18 | } 19 | 20 | export interface FetchMock 21 | extends jest.MockInstance, [string | Request | undefined, RequestInit | undefined]> { 22 | (input: string | Request, init?: RequestInit): Promise; 23 | 24 | // Response mocking 25 | mockResponse(fn: MockResponseInitFunction): FetchMock; 26 | mockResponse(response: string, responseInit?: MockParams): FetchMock; 27 | 28 | mockResponseOnce(fn: MockResponseInitFunction): FetchMock; 29 | mockResponseOnce(response: string, responseInit?: MockParams): FetchMock; 30 | 31 | // alias for mockResponseOnce 32 | once(fn: MockResponseInitFunction): FetchMock; 33 | once(url: string, responseInit?: MockParams): FetchMock; 34 | 35 | mockResponses(...responses: Array): FetchMock; 36 | 37 | // Error/Reject mocking 38 | mockReject(error?: ErrorOrFunction): FetchMock; 39 | mockRejectOnce(error?: ErrorOrFunction): FetchMock; 40 | 41 | mockAbort(): FetchMock; 42 | mockAbortOnce(): FetchMock; 43 | 44 | // Conditional Mocking 45 | isMocking(input: string | Request): boolean; 46 | 47 | doMock(fn?: MockResponseInitFunction): FetchMock; 48 | doMock(response: string, responseInit?: MockParams): FetchMock; 49 | 50 | doMockOnce(fn?: MockResponseInitFunction): FetchMock; 51 | doMockOnce(response: string, responseInit?: MockParams): FetchMock; 52 | // alias for doMockOnce 53 | mockOnce(fn?: MockResponseInitFunction): FetchMock; 54 | mockOnce(response: string, responseInit?: MockParams): FetchMock; 55 | 56 | doMockIf(urlOrPredicate: UrlOrPredicate, fn?: MockResponseInitFunction): FetchMock; 57 | doMockIf(urlOrPredicate: UrlOrPredicate, response: string, responseInit?: MockParams): FetchMock; 58 | // alias for doMockIf 59 | mockIf(urlOrPredicate: UrlOrPredicate, fn?: MockResponseInitFunction): FetchMock; 60 | mockIf(urlOrPredicate: UrlOrPredicate, response: string, responseInit?: MockParams): FetchMock; 61 | 62 | doMockOnceIf(urlOrPredicate: UrlOrPredicate, fn?: MockResponseInitFunction): FetchMock; 63 | doMockOnceIf(urlOrPredicate: UrlOrPredicate, response: string, responseInit?: MockParams): FetchMock; 64 | // alias for doMocKOnceIf 65 | mockOnceIf(urlOrPredicate: UrlOrPredicate, fn?: MockResponseInitFunction): FetchMock; 66 | mockOnceIf(urlOrPredicate: UrlOrPredicate, response: string, responseInit?: MockParams): FetchMock; 67 | 68 | dontMock(fn?: MockResponseInitFunction): FetchMock; 69 | dontMock(response: string, responseInit?: MockParams): FetchMock; 70 | 71 | dontMockOnce(fn?: MockResponseInitFunction): FetchMock; 72 | dontMockOnce(response: string, responseInit?: MockParams): FetchMock; 73 | 74 | dontMockIf(urlOrPredicate: UrlOrPredicate, fn?: MockResponseInitFunction): FetchMock; 75 | dontMockIf(urlOrPredicate: UrlOrPredicate, response: string, responseInit?: MockParams): FetchMock; 76 | 77 | dontMockOnceIf(urlOrPredicate: UrlOrPredicate, fn?: MockResponseInitFunction): FetchMock; 78 | dontMockOnceIf(urlOrPredicate: UrlOrPredicate, response: string, responseInit?: MockParams): FetchMock; 79 | 80 | resetMocks(): void; 81 | enableMocks(): void; 82 | disableMocks(): void; 83 | } 84 | 85 | export interface MockParams { 86 | status?: number; 87 | statusText?: string; 88 | headers?: string[][] | { [key: string]: string }; // HeadersInit 89 | url?: string; 90 | /** Set >= 1 to have redirected return true. Only applicable to Node.js */ 91 | counter?: number; 92 | } 93 | 94 | export interface MockResponseInit extends MockParams { 95 | body?: string; 96 | init?: MockParams; 97 | } 98 | 99 | export type ErrorOrFunction = Error | ((...args: any[]) => Promise); 100 | export type UrlOrPredicate = string | RegExp | ((input: Request) => boolean); 101 | 102 | export type MockResponseInitFunction = (request: Request) => MockResponseInit | string | Promise; 103 | 104 | // alias of fetchMock.enableMocks() for ES6 import syntax to not clash with other libraries 105 | export function enableFetchMocks(): void; 106 | // alias of fetchMock.disableMocks() for ease of ES6 import syntax to not clash with other libraries 107 | export function disableFetchMocks(): void; 108 | 109 | declare const fetchMock: FetchMock; 110 | 111 | export default fetchMock; 112 | -------------------------------------------------------------------------------- /types/test.ts: -------------------------------------------------------------------------------- 1 | import fm, { enableFetchMocks, disableFetchMocks, MockResponseInit } from 'jest-fetch-mock'; 2 | 3 | fetchMock.mockResponse(JSON.stringify({foo: "bar"})); 4 | fetchMock.mockResponse(JSON.stringify({foo: "bar"}), { 5 | status: 200, 6 | headers: [ 7 | ["Content-Type", "application/json"] 8 | ] 9 | }); 10 | fetchMock.mockResponse(JSON.stringify({foo: "bar"}), {}); 11 | fetchMock.mockResponse(someAsyncHandler); 12 | fetchMock.mockResponse(someAsyncStringHandler); 13 | 14 | fetchMock.mockResponseOnce(JSON.stringify({foo: "bar"})); 15 | fetchMock.mockResponseOnce(JSON.stringify({foo: "bar"}), { 16 | status: 200, 17 | headers: [ 18 | ["Content-Type", "application/json"] 19 | ] 20 | }); 21 | fetchMock.mockResponseOnce(JSON.stringify({foo: "bar"}), {}); 22 | fetchMock.mockResponseOnce(someAsyncHandler); 23 | fetchMock.mockResponseOnce(someAsyncStringHandler); 24 | 25 | fetchMock.once(JSON.stringify({foo: "bar"})); 26 | fetchMock.once(JSON.stringify({foo: "bar"}), { 27 | status: 200, 28 | headers: [ 29 | ["Content-Type", "application/json"] 30 | ] 31 | }); 32 | fetchMock.once(JSON.stringify({foo: "bar"}), {}); 33 | fetchMock.once(someAsyncHandler); 34 | 35 | fetchMock.mockResponses(JSON.stringify({}), JSON.stringify({foo: "bar"})); 36 | fetchMock.mockResponses(someAsyncHandler, someAsyncHandler); 37 | fetchMock.mockResponses(JSON.stringify({}), someAsyncHandler); 38 | fetchMock.mockResponses(someAsyncHandler, JSON.stringify({})); 39 | fetchMock.mockResponses(someAsyncHandler); 40 | fetchMock.mockResponses([JSON.stringify({foo: "bar"}), {status: 200}]); 41 | fetchMock.mockResponses( 42 | someSyncHandler, 43 | someAsyncHandler, 44 | someSyncStringHandler, 45 | someAsyncStringHandler, 46 | [JSON.stringify({foo: "bar"}), {status: 200}] 47 | ); 48 | 49 | fetchMock.mockReject(new Error("oops")); 50 | fetchMock.mockReject(someAsyncHandler); 51 | 52 | fetchMock.mockRejectOnce(new Error("oops")); 53 | fetchMock.mockRejectOnce(someAsyncHandler); 54 | fetchMock.resetMocks(); 55 | fetchMock.enableMocks(); 56 | fetchMock.disableMocks(); 57 | 58 | fetchMock.isMocking("http://bar"); 59 | fetchMock.isMocking(new Request("http://bang")); 60 | 61 | fetchMock.doMockIf('http://foo'); 62 | fetchMock.doMockIf(/bar/); 63 | fetchMock.doMockIf((input: Request|string) => true); 64 | fetchMock.mockIf('http://foo'); 65 | fetchMock.mockIf(/bar/); 66 | fetchMock.mockIf((input: Request|string) => true); 67 | fetchMock.dontMockIf('http://foo'); 68 | fetchMock.dontMockIf(/bar/); 69 | fetchMock.dontMockIf((input: Request|string) => true); 70 | fetchMock.doMockOnceIf('http://foo'); 71 | fetchMock.doMockOnceIf(/bar/); 72 | fetchMock.doMockOnceIf((input: Request|string) => true); 73 | fetchMock.mockOnceIf('http://foo'); 74 | fetchMock.mockOnceIf(/bar/); 75 | fetchMock.mockOnceIf((input: Request|string) => true); 76 | fetchMock.dontMockOnceIf('http://foo'); 77 | fetchMock.dontMockOnceIf(/bar/); 78 | fetchMock.dontMockOnceIf((input: Request|string) => true); 79 | 80 | fetchMock.doMock(); 81 | fetchMock.dontMock(); 82 | fetchMock.doMockOnce(); 83 | fetchMock.dontMockOnce(); 84 | fetchMock.mockOnce(); 85 | 86 | async function someAsyncHandler(): Promise { 87 | return { 88 | status: 200, 89 | body: await someAsyncStringHandler() 90 | }; 91 | } 92 | 93 | function someSyncHandler(): MockResponseInit { 94 | return { 95 | status: 200, 96 | body: someSyncStringHandler() 97 | }; 98 | } 99 | 100 | async function someAsyncStringHandler(): Promise { 101 | return Promise.resolve(someSyncStringHandler()); 102 | } 103 | 104 | function someSyncStringHandler(): string { 105 | return JSON.stringify({foo: "bar"}); 106 | } 107 | 108 | enableFetchMocks(); 109 | disableFetchMocks(); 110 | fm.enableMocks(); 111 | fm.disableMocks(); 112 | fm.doMock(); 113 | fm.dontMock(); 114 | fm.doMockOnce(); 115 | fm.dontMockOnce(); 116 | fm.mockOnce(); 117 | -------------------------------------------------------------------------------- /types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": [ 5 | "es2015" 6 | ], 7 | "baseUrl": ".", 8 | "paths": { 9 | "jest-fetch-mock": [ 10 | "." 11 | ] 12 | }, 13 | "noImplicitAny": true, 14 | "noImplicitThis": true, 15 | "strictNullChecks": true, 16 | "strictFunctionTypes": true, 17 | "noEmit": true, 18 | "forceConsistentCasingInFileNames": true 19 | } 20 | } 21 | --------------------------------------------------------------------------------