Skip to content

Commit

Permalink
completed cheatsheet
Browse files Browse the repository at this point in the history
  • Loading branch information
wheresrhys committed Mar 22, 2020
1 parent 7efa6c5 commit 1dfc6c2
Showing 1 changed file with 87 additions and 24 deletions.
111 changes: 87 additions & 24 deletions docs/cheatsheet.md
Original file line number Diff line number Diff line change
@@ -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()`
Expand All @@ -16,60 +43,96 @@ 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'}}`
- Match **part of a JSON body** `{body: {prop1: 'val1'}, matchPartialBody: true}`
- 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)`


0 comments on commit 1dfc6c2

Please sign in to comment.