diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index 7ce2fa26..f59a97b0 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -1,10 +1,37 @@ # fetch-mock cheatsheet +*This is a first draft - please feedback in the [issues](https://github.com/wheresrhys/fetch-mock/issues)* + +- [Installation](#installation) +- [Set up and teardown](#setup-and-teardown) +- [Request matching](#request-matching) +- [Response configuration](#response-configuration) +- [Inspecting calls](#inspecting-calls) + ## Installation -- .sandbox() +`npm i -D fetch-mock` (or `npm i -D fetch-mock-jest` if using jest) + +### Global fetch +import/require the fetch-mock/fetch-mock-jest library. For the vast majority of test toolchains this _should_ jsut work without any additional wiring. + +### Local fetch with jest +```js +jest.mock('node-fetch', () => require('fetch-mock-jest').sandbox()) +const fetchMock = require('node-fetch') +``` + +### Local fetch with other test runners +```js +// pass this mock into your module mocking tool of choice +// Example uses https://www.npmjs.com/package/proxyquire +const fetchMock = require('fetch-mock').sandbox() +proxyquire('./my-module', {'node-fetch': fetchMock}) +``` + +## Setup and teardown -## Mock setup methods +### Mock setup methods All these methods can be chained e.g. `fetchMock.getAny(200).catch(400)` - Stub fetch and **define a route** `.mock(matcher, response)` - Stub fetch **without a route** `.mock()` @@ -16,41 +43,42 @@ All these methods can be chained e.g. `fetchMock.getAny(200).catch(400)` - Add a mock that only responds to the given **method** `.get()`, `.post()`, `.put()`, `.delete()`, `.head()`, `.patch()` - **Combinations** of the above behaviours `getAny()`, `getOnce()`, `getAnyOnce()`, `postAny()` ... -## Tear down methods +### Tear down methods - Remove all mocks and history `.restore()`, `.reset()` - Discard all recorded calls, but keep defined routes `.resetHistory()` - Discard all routes, but keep defined recorded calls`.resetBehavior()` ## Request matching -### Reference request The following request would be matched by all the mocks described below: ```js fetch('http://example.com/users/bob?q=rita', { method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: { - prop1: 'val1', - prop2: 'val2' - } + headers: {'Content-Type': 'application/json'}, + body: '{"prop1": "val1", "prop2": "val2"}' }) ``` -### Matching urls -All the below can be passed as the first argument into `.mock()`, (or as the `url` property on the first parameter for combining with other matchers). +### Urls +Can be passed as +- the first argument `.mock('blah', 200)` +- a `url` property on the first argument `.mock({url: 'blah'}, 200)` + +#### Patterns - Match **any** url `'*'` - Match **exact** url `'http://example.com/users/bob?q=rita'` - Match **beginning** `'begin:http://example.com'` - Match **end** `'end:bob?q=rita'` - Match **path** `'path:/users/bob'` -- Match using a **glob** expression `'glob:http://example.{com,gov}/*'` -- Match using **express** syntax `'express:/users/:name'` -- Match using a **RegExp** `/\/users\/.*/` +- Match a **glob** expression `'glob:http://example.{com,gov}/*'` +- Match **express** syntax `'express:/users/:name'` +- Match a **RegExp** `/\/users\/.*/` + +#### Naming routes +When defining multiple mocks on the same underlying url (e.g. differing only on headers), set a `name` property on the matcher of each route. -### Matching other parts of the request -The following should be passed as properties of an object as the first argument of `.mock()`. Multiple rules can be combined together. +### Other properties +The following should be passed as properties of the first argument of `.mock()`. - Match the request **method** `{method: 'POST'}` - Match **headers** `{headers: {'Content-Type': 'application/json'}}` - Match a **JSON body** `{body: {prop1: 'val1', prop2: 'val2'}}` @@ -58,18 +86,53 @@ The following should be passed as properties of an object as the first argument - Match **query** parameters `{query: {q: 'rita'}}` - Match express **path parameters** `{url: 'express:/users/:name', params: {name: 'bob'}}` -### Custom matching +### Custom Match on any condition you like by: - using a function `{functionMatcher: (url, options, request) => url.length > 100}` - defining your own declarative matchers with [`addMatcher()`](http://www.wheresrhys.co.uk/fetch-mock/#api-mockingadd-matcher), e.g. something like this would be possible `{isCorsRequest: true, hasBody: true}` -## Responses -- repeat -- delay +## Response configuration +Responses are configured with eth second, and sometimes third, arguments passed to `.mock()` (or the first and second argument of `.any()` or `.catch()`). Where only one code sample is given below, it describes the second argument; otherwise the second and third are given. *[Note - in the next major version these will all be available on the second argument]* + +- **`Response`** instance `new Response('hello world')` +- **status code** `200` +- **text** `hello world` +- **JSON** `{prop: 'val'}` +- **streamed content** `new Blob()`, `{sendAsJson: false}` +- **headers** `{body: 'hello world', status: 200, headers: {'Content-Type': 'text'}` +- **throw** an error `{throws: new Error('fetch failed')}` +- **redirect** `{redirectUrl: 'http://other.site, status: 302}` +- **function** ``(url, options, request) => `Content from ${url}` `` + +### Timing and repetition +- Respond a specified **number of times** `200`, `{repeat: 3}` +- **Delay** by a number of milliseconds `200`, `{delay: 2000}` +- Custom async behaviour using a **Promise** `myPromise.then(200)` + +Functions and Promises can be nested to any depth to implement complex race conditions + ## Inspecting calls -- done -### Naming routes +`.calls()` retrieves a list of all calls matching certain conditions. It return an array of `[url, options]` arrays, which also have a `.request` property containng the original `Request` instance. + +### Filtering +- **all** calls `.calls()` +- **matched** calls `.calls(true)` or `.calls('matched')` +- **unmatched** calls `.calls(false)` or `.calls('unmatched')` +- calls to a **named route** `.calls('My route`) +- calls to a **url pattern** used in a route `.calls('end:/path/bob`) +- calls matching a **matcher** `.calls(anyValidMatcher)` +- calls filtered by **method** `.calls(anyValidMatcher, 'POST')` + +### Shortcut methods +These accept the same filters as above, but give a quicker route to answering common questions +- Do **any calls** match the filter? `.called()` +- What was the **last call** `.lastCall()` +- What was the **last url** `.lastUrl()` +- What was the **last options** `.lastOptions()` +### Completion +- Check if **all routes** have been **called as expected** `.done()` +- Check if **all routes matching the filter** have been **called as expected** `.done(filter)` (filter must be a **route name** or **url pattern**) - Wait for all fetches to respond `.flush()` (pass in `true` to wait for all bodies to be streamed). e.g. `await fetchMock.flush(true)`