UNPKG

6.57 kBJavaScriptView Raw
1const crossFetch = require('cross-fetch')
2global.fetch = crossFetch
3global.Response = crossFetch.Response
4global.Headers = crossFetch.Headers
5global.Request = crossFetch.Request
6
7if (!Promise) {
8 Promise = require('promise-polyfill')
9} else if (!Promise.finally) {
10 Promise.finally = require('promise-polyfill').finally
11}
12
13const ActualResponse = Response
14
15function responseWrapper(body, init) {
16 if (
17 body &&
18 typeof body.constructor === 'function' &&
19 body.constructor.__isFallback
20 ) {
21 const response = new ActualResponse(null, init)
22 response.body = body
23
24 const actualClone = response.clone
25 response.clone = () => {
26 const clone = actualClone.call(response)
27 const [body1, body2] = body.tee()
28 response.body = body1
29 clone.body = body2
30 return clone
31 }
32
33 return response
34 }
35
36 return new ActualResponse(body, init)
37}
38
39function responseInit(resp, init) {
40 if (typeof resp.init === 'object') {
41 return resp.init
42 } else {
43 init = Object.assign({}, init || {})
44 for (const field of ['status', 'statusText', 'headers', 'url']) {
45 if (field in resp) {
46 init[field] = resp[field]
47 }
48 }
49 return init
50 }
51}
52
53function requestMatches(urlOrPredicate) {
54 if (typeof urlOrPredicate === 'function') {
55 return urlOrPredicate
56 }
57 const predicate =
58 urlOrPredicate instanceof RegExp
59 ? input => urlOrPredicate.exec(input) !== null
60 : input => input === urlOrPredicate
61 return input => {
62 const requestUrl = typeof input === 'object' ? input.url : input
63 return predicate(requestUrl)
64 }
65}
66
67function requestNotMatches(urlOrPredicate) {
68 const matches = requestMatches(urlOrPredicate)
69 return input => {
70 return !matches(input)
71 }
72}
73
74const isFn = unknown => typeof unknown === 'function'
75
76const isMocking = jest.fn(() => true)
77
78const abortError = () =>
79 new DOMException('The operation was aborted. ', 'AbortError')
80
81const abort = () => {
82 throw abortError()
83}
84
85const abortAsync = () => {
86 return Promise.reject(abortError())
87}
88
89const normalizeResponse = (bodyOrFunction, init) => (input, reqInit) => {
90 const request = normalizeRequest(input, reqInit)
91 return isMocking(input, reqInit)
92 ? isFn(bodyOrFunction)
93 ? bodyOrFunction(request).then(resp => {
94 if (request.signal && request.signal.aborted) {
95 abort()
96 }
97 return typeof resp === 'string'
98 ? responseWrapper(resp, init)
99 : responseWrapper(resp.body, responseInit(resp, init))
100 })
101 : new Promise((resolve, reject) => {
102 if (request.signal && request.signal.aborted) {
103 reject(abortError())
104 return
105 }
106 resolve(responseWrapper(bodyOrFunction, init))
107 })
108 : crossFetch.fetch(input, reqInit)
109}
110
111const normalizeRequest = (input, reqInit) => {
112 if (input instanceof Request) {
113 if (input.signal && input.signal.aborted) {
114 abort()
115 }
116 return input
117 } else if (typeof input === 'string') {
118 if (reqInit && reqInit.signal && reqInit.signal.aborted) {
119 abort()
120 }
121 return new Request(input, reqInit)
122 } else {
123 throw new TypeError('Unable to parse input as string or Request')
124 }
125}
126
127const normalizeError = errorOrFunction =>
128 isFn(errorOrFunction)
129 ? errorOrFunction
130 : () => Promise.reject(errorOrFunction)
131
132const fetch = jest.fn(normalizeResponse(''))
133fetch.Headers = Headers
134fetch.Response = responseWrapper
135fetch.Request = Request
136fetch.mockResponse = (bodyOrFunction, init) =>
137 fetch.mockImplementation(normalizeResponse(bodyOrFunction, init))
138
139fetch.mockReject = errorOrFunction =>
140 fetch.mockImplementation(normalizeError(errorOrFunction))
141
142fetch.mockAbort = () => fetch.mockImplementation(abortAsync)
143fetch.mockAbortOnce = () => fetch.mockImplementationOnce(abortAsync)
144
145const mockResponseOnce = (bodyOrFunction, init) =>
146 fetch.mockImplementationOnce(normalizeResponse(bodyOrFunction, init))
147
148fetch.mockResponseOnce = mockResponseOnce
149
150fetch.once = mockResponseOnce
151
152fetch.mockRejectOnce = errorOrFunction =>
153 fetch.mockImplementationOnce(normalizeError(errorOrFunction))
154
155fetch.mockResponses = (...responses) => {
156 responses.forEach(response => {
157 if (Array.isArray(response)) {
158 const [body, init] = response
159 fetch.mockImplementationOnce(normalizeResponse(body, init))
160 } else {
161 fetch.mockImplementationOnce(normalizeResponse(response))
162 }
163 })
164 return fetch
165}
166
167fetch.isMocking = isMocking
168
169fetch.mockIf = fetch.doMockIf = (urlOrPredicate, bodyOrFunction, init) => {
170 isMocking.mockImplementation(requestMatches(urlOrPredicate))
171 if (bodyOrFunction) {
172 fetch.mockResponse(bodyOrFunction, init)
173 }
174 return fetch
175}
176
177fetch.dontMockIf = (urlOrPredicate, bodyOrFunction, init) => {
178 isMocking.mockImplementation(requestNotMatches(urlOrPredicate))
179 if (bodyOrFunction) {
180 fetch.mockResponse(bodyOrFunction, init)
181 }
182 return fetch
183}
184
185fetch.mockOnceIf = fetch.doMockOnceIf = (
186 urlOrPredicate,
187 bodyOrFunction,
188 init
189) => {
190 isMocking.mockImplementationOnce(requestMatches(urlOrPredicate))
191 if (bodyOrFunction) {
192 mockResponseOnce(bodyOrFunction, init)
193 }
194 return fetch
195}
196
197fetch.dontMockOnceIf = (urlOrPredicate, bodyOrFunction, init) => {
198 isMocking.mockImplementationOnce(requestNotMatches(urlOrPredicate))
199 if (bodyOrFunction) {
200 mockResponseOnce(bodyOrFunction, init)
201 }
202 return fetch
203}
204
205fetch.dontMock = () => {
206 isMocking.mockImplementation(() => false)
207 return fetch
208}
209
210fetch.dontMockOnce = () => {
211 isMocking.mockImplementationOnce(() => false)
212 return fetch
213}
214
215fetch.doMock = (bodyOrFunction, init) => {
216 isMocking.mockImplementation(() => true)
217 if (bodyOrFunction) {
218 fetch.mockResponse(bodyOrFunction, init)
219 }
220 return fetch
221}
222
223fetch.mockOnce = fetch.doMockOnce = (bodyOrFunction, init) => {
224 isMocking.mockImplementationOnce(() => true)
225 if (bodyOrFunction) {
226 mockResponseOnce(bodyOrFunction, init)
227 }
228 return fetch
229}
230
231fetch.resetMocks = () => {
232 fetch.mockReset()
233 isMocking.mockReset()
234
235 // reset to default implementation with each reset
236 fetch.mockImplementation(normalizeResponse(''))
237 fetch.doMock()
238 fetch.isMocking = isMocking
239}
240
241fetch.enableMocks = fetch.enableFetchMocks = () => {
242 global.fetchMock = global.fetch = fetch
243 try {
244 jest.setMock('node-fetch', fetch)
245 } catch (error) {
246 //ignore
247 }
248}
249
250fetch.disableMocks = fetch.disableFetchMocks = () => {
251 global.fetch = crossFetch
252 try {
253 jest.dontMock('node-fetch')
254 } catch (error) {
255 //ignore
256 }
257}
258
259module.exports = fetch.default = fetch