Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add cy.route2 recipe #555

Merged
merged 13 commits into from
Sep 9, 2020
Prev Previous commit
Next Next commit
update stub spec
  • Loading branch information
bahmutov committed Sep 9, 2020
commit 360b3c3746d56bd43d280c362daf9524c75ab07b
1 change: 1 addition & 0 deletions examples/stubbing-spying__route2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

- [control-clock-spec.js](cypress/integration/control-clock-spec.js) shows how to reply with different responses to an ajax request
- [spy-on-fetch-spec.js](cypress/integration/spy-on-fetch-spec.js) shows how to spy on the `fetch` call
- [stub-fetch-spec.js](cypress/integration/stub-fetch-spec.js) shows how to stub `fetch` calls from the application
4 changes: 4 additions & 0 deletions examples/stubbing-spying__route2/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ function getFavoriteFruits () {

fetch('/favorite-fruits')
.then((response) => {
/* eslint-disable-next-line no-console */
console.log('fetch response', response)
if (response.ok) {
return response.json()
}
Expand All @@ -21,6 +23,8 @@ function getFavoriteFruits () {
throw new Error(errorMessage)
})
.then((response) => {
/* eslint-disable-next-line no-console */
console.log('server response', response)
updateFavoriteFruits(response.length ? response : 'No favorites')
})
.catch((error) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ describe('route2', () => {

it('requests favorite fruits', function () {
cy.wait('@fetchFruits')
// TODO: can we inspect the response object from the server?
// like response body...

cy.get('.favorite-fruits li').should('have.length', 5)
})
})
Expand Down
221 changes: 106 additions & 115 deletions examples/stubbing-spying__route2/cypress/integration/stub-fetch-spec.js
Original file line number Diff line number Diff line change
@@ -1,142 +1,133 @@
/// <reference types="Cypress" />
// @ts-check

describe('stubbing', function () {
it('shows no Response message', () => {
cy.visit('/', {
onBeforeLoad (win) {
cy.stub(win, 'fetch').withArgs('/favorite-fruits')
.resolves({
ok: true,
json: () => [],
})
},

describe('route2', () => {
context('stubbing', function () {
it('shows no Response message', () => {
// stub server response with []
cy.route2('/favorite-fruits', JSON.stringify([]))
cy.visit('/')
cy.contains('No favorites').should('be.visible')
})

cy.contains('No favorites').should('be.visible')
})
it('stubs fetch to test loading indicator', () => {
cy.route2('/favorite-fruits', (req) => {
req.reply((res) => {
res.delay(2000).send(['Pineapple 🍍'])
})
})

it('directly stubs window.fetch to test loading indicator', () => {
// stub the "fetch(/favorite-fruits)" call from the app
cy.visit('/', {
onBeforeLoad (win) {
cy.stub(win, 'fetch').withArgs('/favorite-fruits')
.resolves(
// use Bluebird promise bundled with Cypress
// to resolve after 2000ms
Cypress.Promise.resolve({
ok: true,
json: () => ['Pineapple 🍍'],
}).delay(2000)
)
},
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 🍍')
})

// 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 () {
// 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.server()
cy.route({
url: '/favorite-fruits',
response: [],
delay: 1000,
})
cy.route2('/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.delay(1000).send(JSON.stringify([]))
})
})

cy.visit('/')
cy.get('.loader').should('be.visible')
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')
})
// 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.server()
cy.route('/favorite-fruits').as('favoriteFruits')
cy.visit('/')
cy.wait('@favoriteFruits')
it('can spy on network calls from the second page', () => {
cy.route2('/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')
})
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', () => {
cy.server()
cy.route('/favorite-fruits', ['apples 🍎'])
cy.visit('/')
cy.contains('apples 🍎')

// change the response before going to the second page
cy.route('/favorite-fruits', ['grapes 🍇'])
cy.contains('a', 'Go to page 2').click()
cy.url().should('match', /\/page2\.html$/)
cy.contains('grapes 🍇')

// change the response before going back to the index page
cy.route('/favorite-fruits', ['kiwi 🥝'])
cy.contains('a', 'Go back').click()
cy.contains('kiwi 🥝')
})
it('can stub network calls for each page', () => {
let k = 0

cy.route2('/favorite-fruits', (req) => {
k += 1
switch (k) {
case 1:
return req.reply(['apples 🍎'])
case 2:
return req.reply(['grapes 🍇'])
default:
return req.reply(['kiwi 🥝'])
}
})

describe('when favorite fruits are returned', function () {
it('displays the list of fruits', function () {
cy.server()
// aliasing allows us to easily get access to our stub
cy.route('/favorite-fruits', ['Apple', 'Banana', 'Cantaloupe']).as('fetchFavorites')
cy.visit('/')
cy.wait('@fetchFavorites')
cy.contains('apples 🍎')

cy.contains('a', 'Go to page 2').click()
cy.url().should('match', /\/page2\.html$/)
cy.contains('grapes 🍇')

cy.get('.favorite-fruits li').as('favoriteFruits')
.should('have.length', 3)
cy.contains('a', 'Go back').click()
cy.contains('kiwi 🥝')
})

cy.get('@favoriteFruits').first()
.should('have.text', 'Apple')
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.route2('/favorite-fruits', ['Apple', 'Banana', 'Cantaloupe']).as('fetchFavorites')
cy.visit('/')
cy.wait('@fetchFavorites')

cy.get('@favoriteFruits').eq(1)
.should('have.text', 'Banana')
cy.get('.favorite-fruits li').as('favoriteFruits')
.should('have.length', 3)

cy.get('@favoriteFruits').eq(2)
.should('have.text', 'Cantaloupe')
})
})
cy.get('@favoriteFruits').first()
.should('have.text', 'Apple')

describe('when no favorite fruits are returned', function () {
it('displays empty message', function () {
cy.server()
cy.route('/favorite-fruits', [])
cy.visit('/')
cy.get('.favorite-fruits').should('have.text', 'No favorites')
cy.get('@favoriteFruits').eq(1)
.should('have.text', 'Banana')

cy.get('@favoriteFruits').eq(2)
.should('have.text', 'Cantaloupe')
})
})
})

describe('when request fails', function () {
it('displays error', function () {
cy.server()
cy.route({
url: '/favorite-fruits',
status: 500,
response: '',
delay: 2000,
headers: {
'status-text': 'Orchard under maintenance',
},
describe('when no favorite fruits are returned', function () {
it('displays empty message', function () {
cy.route2('/favorite-fruits', JSON.stringify([]))
cy.visit('/')
cy.get('.favorite-fruits').should('have.text', 'No favorites')
})
})

cy.visit('/')
describe('when request fails', function () {
it('displays error', function () {
cy.route2('/favorite-fruits', (req) => {
req.reply({
statusCode: 500,
body: '',
headers: {
'status-text': 'Orchard under maintenance',
},
})
})

cy.get('.favorite-fruits')
.should('have.text', 'Failed loading favorite fruits: Orchard under maintenance')
cy.visit('/')

cy.get('.favorite-fruits')
.should('have.text', 'Failed loading favorite fruits: Orchard under maintenance')
})
})
})
})