forked from cypress-io/cypress-example-recipes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
stub-fetch-spec.cy.js
337 lines (282 loc) · 10.3 KB
/
stub-fetch-spec.cy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/// <reference types="Cypress" />
/* eslint-disable no-console */
describe('intercept', () => {
context('stubbing', function () {
it('shows no Response message', () => {
// stub server response with []
cy.intercept('/favorite-fruits', [])
cy.visit('/')
cy.contains('No favorites').should('be.visible')
})
it('modifies the response from the server to insert Kiwi', () => {
cy.intercept('favorite-fruits', (req) => {
req.reply((res) => {
// add Kiwi to the list received from the server
console.log('original response from the server is %s %o', typeof res.body, res.body)
const list = res.body
list.push('Kiwi')
res.send(list)
})
})
cy.visit('/')
// check if Kiwi is the last fruit
cy.get('li').should('have.length.gt', 3)
.last().should('contain', 'Kiwi')
})
it('stubs fetch to test loading indicator', () => {
cy.intercept('/favorite-fruits', (req) => {
req.reply((res) => {
res.setDelay(2000).send(['Pineapple 🍍'])
})
})
cy.visit('/')
// at first, the app is showing the loading indicator
cy.get('.loader').should('be.visible')
// once the promise is resolved, the loading indicator goes away
cy.get('.loader').should('not.exist')
cy.contains('li', 'Pineapple 🍍')
})
// A big advantage of controlling the response is we can test
// how our app handles a slow response, which normally might be
// difficult against a fast development server
it('shows loader while fetching fruits', function () {
// stub the XHR request from the app
cy.intercept('/favorite-fruits', (req) => {
req.reply((res) => {
// hmm, every time we want to return an empty list
// we need to stringify it, otherwise the stub does not ... stub
res.setDelay(1000).send([])
})
})
cy.visit('/')
cy.get('.loader').should('be.visible')
// once the network call finishes, the loader goes away
cy.get('.loader').should('not.exist')
cy.contains('.favorite-fruits', 'No favorites')
})
// NOTE: this does not work: cannot use cy commands inside the request handler
it.skip('shows loading indicator (alternative)', function () {
cy.intercept('/favorite-fruits', (req) => {
req.reply((res) => {
cy.get('.loader').should('be.visible')
res.send([])
})
})
cy.visit('/')
// once the network call finishes, the loader goes away
cy.get('.loader').should('not.exist')
cy.contains('.favorite-fruits', 'No favorites')
})
it('can spy on network calls from the second page', () => {
cy.intercept('/favorite-fruits').as('favoriteFruits')
cy.visit('/')
cy.wait('@favoriteFruits')
cy.contains('a', 'Go to page 2').click()
cy.url().should('match', /\/page2\.html$/)
// the second page also requests the fruits
cy.wait('@favoriteFruits')
})
it('can stub network calls for each page', () => {
let k = 0
// return difference responses on each call
cy.intercept('/favorite-fruits', (req) => {
k += 1
switch (k) {
case 1:
return req.reply(['apples 🍎'])
case 2:
return req.reply(['grapes 🍇'])
default:
return req.reply(['kiwi 🥝'])
}
})
cy.visit('/')
cy.contains('apples 🍎')
cy.contains('a', 'Go to page 2').click()
cy.url().should('match', /\/page2\.html$/)
cy.contains('grapes 🍇')
cy.contains('a', 'Go back').click()
cy.contains('kiwi 🥝')
})
describe('when favorite fruits are returned', function () {
it('displays the list of fruits', function () {
// aliasing allows us to easily get access to our stub
cy.intercept('/favorite-fruits', ['Apple', 'Banana', 'Cantaloupe']).as('fetchFavorites')
cy.visit('/')
cy.wait('@fetchFavorites')
cy.get('.favorite-fruits li').as('favoriteFruits')
.should('have.length', 3)
cy.get('@favoriteFruits').first()
.should('have.text', 'Apple')
cy.get('@favoriteFruits').eq(1)
.should('have.text', 'Banana')
cy.get('@favoriteFruits').eq(2)
.should('have.text', 'Cantaloupe')
})
it('shows fruits', function () {
const fruits = ['Apple', 'Banana', 'Cantaloupe']
cy.intercept('/favorite-fruits', fruits)
cy.visit('/')
fruits.forEach((fruit) => {
cy.contains('.favorite-fruits li', fruit)
})
})
})
describe('when no favorite fruits are returned', function () {
it('displays empty message', function () {
cy.intercept('/favorite-fruits', [])
cy.visit('/')
cy.get('.favorite-fruits').should('have.text', 'No favorites')
})
})
describe('when request fails', function () {
it('displays error', function () {
// you can be explicit with the reply
cy.intercept('/favorite-fruits', (req) => {
req.reply({
statusCode: 500,
body: '',
headers: {
'status-text': 'Orchard under maintenance',
},
})
})
cy.visit('/')
cy.get('.favorite-fruits')
.should('have.text', 'Failed loading favorite fruits: Orchard under maintenance')
})
})
it('displays error (short)', function () {
// you can give the response object with status code
cy.intercept({ url: '/favorite-fruits' }, {
statusCode: 500,
body: '',
headers: {
'status-text': 'Orchard under maintenance',
},
})
cy.visit('/')
cy.get('.favorite-fruits')
.should('have.text', 'Failed loading favorite fruits: Orchard under maintenance')
})
it('stubs a request that goes to another domain', () => {
cy.visit('/')
const users = [{
id: '1',
email: '[email protected]',
username: 'Test User',
}]
cy.intercept({ url: 'https://jsonplaceholder.cypress.io/users*' }, {
body: users,
headers: {
'access-control-allow-origin': Cypress.config('baseUrl'),
},
}).as('users')
cy.get('#load-users').click()
cy.wait('@users').its('response.body')
.should('have.length', 1)
.its('0') // grab the first user from the list
.should('deep.equal', users[0])
// the user should be shown on the page
cy.contains('.user', `${users[0].id} - ${users[0].email}`).should('be.visible')
})
it('stub or spy depending on the object sent', () => {
cy.visit('/')
cy.intercept('POST', '/users', (req) => {
// inspect the request to decide if we want to mock it or not
if (req.body.id === 101) {
// ok, let's stub it, the server usually responds with the same object
return req.reply(req.body)
}
// if we do not call req.reply, then the request goes to the server
// and we can still spy on it
}).as('postUser')
cy.get('#post-user').click()
cy.wait('@postUser')
})
it('can set an alias depending on the request', () => {
cy.visit('/')
cy.intercept('GET', '/users*', (req) => {
// Inspect the request, and if it what we are looking for,
// set the alias to assert against later. Very useful for
// GraphQL requests!
if (req.url.endsWith('/users?_limit=5')) {
req.alias = 'load5'
}
})
cy.get('#load-users').click()
cy.get('#load-five-users').click()
cy.wait('@load5') // the second request created this alias dynamically
.its('response.body').should('have.length', 5)
})
it('any errors from the intercept fail the test', (done) => {
cy.visit('/')
const errorMessage = 'Intercept gone wrong!'
cy.intercept('POST', '/users', () => {
// imagine we have an error in our intercept logic
// it will fail the test
throw new Error(errorMessage)
})
cy.on('fail', (e) => {
if (!e.message === errorMessage) {
// hmm, unexpected error text
// return and fail the test
throw e
}
done()
})
cy.get('#post-user').click()
})
it('stubs all non-stubbed Ajax with 404', () => {
// similar to the deprecated cy.server({ force:404 })
// stop all fall-through Ajax application/json requests with a 404
cy.intercept({
headers: {
accept: 'application/json',
},
}, {
statusCode: 404,
})
// we want to stub this specific JSON call only
cy.intercept('/favorite-fruits', { fixture: 'fruits.json' })
cy.visit('/')
cy.get('.favorite-fruits li').should('have.length', 3)
// let's try non-stubbed network call - it should fail
cy.get('#load-users').click()
cy.contains('#users', 'Not Found').should('be.visible')
// but we can still fetch fruits
cy.reload()
cy.get('.favorite-fruits li').should('have.length', 3)
// yet another fetch is blocked
cy.get('#load-five-users').click()
cy.contains('#users', 'Not Found').should('be.visible')
})
it('stubs all Ajax but fruits with 404', () => {
// similar to the deprecated cy.server({ force:404 })
// we want to stub all Ajax calls but GET /favorite-fruits
// now let's stop all other Ajax application/json requests
cy.intercept('*', (req) => {
if (req.method === 'GET' && req.url.endsWith('/favorite-fruits')) {
// let the request go to the server
return
}
if (req.headers.accept === 'application/json') {
req.reply({
statusCode: 404,
})
}
})
cy.visit('/')
cy.get('.favorite-fruits li').should('have.length', 5)
// let's try non-stubbed network call - it should fail
cy.get('#load-users').click()
cy.contains('#users', 'Not Found').should('be.visible')
// but we can still fetch fruits
cy.reload()
cy.get('.favorite-fruits li').should('have.length', 5)
// yet another fetch is blocked
cy.get('#load-five-users').click()
cy.contains('#users', 'Not Found').should('be.visible')
})
})
})