Skip to content
This repository has been archived by the owner on Mar 5, 2022. It is now read-only.

Commit

Permalink
break: use Cypress commands and aliases, version bumps and module loa…
Browse files Browse the repository at this point in the history
…ding tweaks (#37)

BREAKING CHANGE: how components are mounted and accessed

* Fixed users spec - had race condition

* Drafted cypress commands

* Lib functions as commands. Use node_modules for loading React

* Switch over to using cy.mount. Docs updated

* Version bumping incl. babel@7

* Added error boundaries - good example component

* Line about err boundary component
  • Loading branch information
Ryan Keller authored and bahmutov committed Feb 17, 2019
1 parent b9b61f4 commit b8ecd2f
Show file tree
Hide file tree
Showing 16 changed files with 1,749 additions and 277 deletions.
8 changes: 7 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{
"presets" : ["es2015", "react"]
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
],
}
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ npm install --save-dev cypress cypress-react-unit-test
// import the component you want to test
import { HelloState } from '../../src/hello-x.jsx'
import React from 'react'
import { mount } from 'cypress-react-unit-test'
describe('HelloState component', () => {
it('works', () => {
// mount the component under test
mount(<HelloState />)
cy.mount(<HelloState />)
// start testing!
cy.contains('Hello Spider-man!')
// mounted component is returned from Cypress.component()
Cypress.component().invoke('setState', {name: 'React'})
Cypress.component().its('state').should('deep.equal', {
name: 'React'
})
// mounted component is aliased as @Component
cy.get('@Component')
.invoke('setState', { name: 'React' })
cy.get('@Component')
.its('state')
.should('deep.equal', { name: 'React' })
// check if GUI has rerendered
cy.contains('Hello React!')
})
Expand Down Expand Up @@ -115,6 +115,7 @@ All components are in [src](src) folder. All tests are in [cypress/integration](
* [counter-spec.js](cypress/integration/counter-spec.js) clicks on the component and confirms the result
* [stateless-spec.js](cypress/integration/stateless-spec.js) shows testing a stateless component from [stateless.jsx](src/stateless.jsx)
* [transpiled-spec.js](cypress/integration/stateless-spec.js) shows testing a component with class properties syntax from [transpiled.jsx](src/stateless.jsx)
* [error-boundary-spec.js](cypress/integration/error-boundary-spec.js) shows testing a component acting as an error boundary from [error-boundary.jsx](src/error-boundary.jsx)
* [users-spec.js](cypress/integration/users-spec.js) shows how to observe XHR requests, mock server responses for component [users.jsx](src/users.jsx)
* [alert-spec.js](cypress/integration/alert-spec.js) shows how to spy on `window.alert` calls from your component [stateless-alert.jsx](src/stateless-alert.jsx)

Expand Down
3 changes: 1 addition & 2 deletions cypress/integration/alert-spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import HelloWorld from '../../src/stateless-alert.jsx'
import React from 'react'
import { mount } from '../../lib'

/* eslint-env mocha */
describe('Stateless alert', () => {
beforeEach(() => {
const spy = cy.spy().as('alert')
cy.on('window:alert', spy)
mount(<HelloWorld name='Alert' />)
cy.mount(<HelloWorld name='Alert' />)
})

it('shows link', () => {
Expand Down
12 changes: 6 additions & 6 deletions cypress/integration/counter-spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Counter } from '../../src/counter.jsx'
import React from 'react'
import { mount } from '../../lib'

/* eslint-env mocha */
describe('Counter', () => {
it('counts clicks', () => {
mount(<Counter />)
cy.mount(<Counter />)
cy.contains('count: 0')
.click()
.contains('count: 1')
Expand All @@ -14,7 +13,7 @@ describe('Counter', () => {
})

it('counts clicks 2', () => {
mount(<Counter />)
cy.mount(<Counter />)
cy.contains('count: 0')
.click()
.contains('count: 1')
Expand All @@ -23,9 +22,9 @@ describe('Counter', () => {
})
})

describe('Counter mounted before each test', () => {
describe('Counter cy.mounted before each test', () => {
beforeEach(() => {
mount(<Counter />)
cy.mount(<Counter />)
})

it('goes to 3', () => {
Expand All @@ -41,7 +40,8 @@ describe('Counter mounted before each test', () => {
.click()
.click()
.click()
Cypress.component().its('state')
cy.get('@Component')
.its('state')
.should('deep.equal', {count: 3})
})
})
43 changes: 43 additions & 0 deletions cypress/integration/error-boundary-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ErrorBoundary } from '../../src/error-boundary.jsx'
import React from 'react'

/* eslint-env mocha */
describe('Error Boundary', () => {
const errorMessage = 'I crashed!'
const ChildWithoutError = () => <h1>Normal Child</h1>
const ChildWithError = () => {
throw new Error(errorMessage)
return null
}

it('renders normal children', () => {
cy.mount(
<ErrorBoundary>
<ChildWithoutError />
</ErrorBoundary>
)
cy.get('h1')
.should('have.text', 'Normal Child')
cy.get('@Component')
.its('state.error')
.should('not.exist')
})

it('on error, display fallback UI', () => {
cy.mount(
<ErrorBoundary>
<ChildWithError />
</ErrorBoundary>
)
cy.get('header h1')
.should('contain', 'Something went wrong.')
cy.get('header h3')
.should('contain', 'failed to load')
cy.get('@Component')
.its('state.error.message')
.should('equal', errorMessage)
cy.get('@Component')
.its('state.error.stack')
.should('contain', 'ChildWithError')
})
})
3 changes: 1 addition & 2 deletions cypress/integration/hello-world-spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { HelloWorld } from '../../src/hello-world.jsx'
import React from 'react'
import { mount } from '../../lib'

/* eslint-env mocha */
describe('HelloWorld component', () => {
it('works', () => {
mount(<HelloWorld />)
cy.mount(<HelloWorld />)
cy.contains('Hello World!')
})
})
15 changes: 8 additions & 7 deletions cypress/integration/hello-x-spec.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { HelloX, HelloState } from '../../src/hello-x.jsx'
import React from 'react'
import { mount } from '../../lib'

/* eslint-env mocha */
describe('HelloX component', () => {
it('works', () => {
mount(<HelloX name="SuperMan" />)
cy.mount(<HelloX name="SuperMan" />)
cy.contains('Hello SuperMan!')
})
})

describe('HelloState component', () => {
it('changes state', () => {
mount(<HelloState />)
cy.mount(<HelloState />)
cy.contains('Hello Spider-man!')
Cypress.component().invoke('setState', {name: 'React'})
Cypress.component().its('state').should('deep.equal', {
name: 'React'
})
const stateToSet = { name: 'React' }
cy.get('@Component')
.invoke('setState', stateToSet)
cy.get('@Component')
.its('state')
.should('deep.equal', stateToSet)
cy.contains('Hello React!')
})
})
3 changes: 1 addition & 2 deletions cypress/integration/stateless-spec.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import HelloWorld from '../../src/stateless.jsx'
import React from 'react'
import { mount } from '../../lib'

/* eslint-env mocha */
describe('Stateless component', () => {
beforeEach(() => {
// pass spy and save it under an alias
// so we can easily get it later with cy.get('@greeting')
const spy = cy.spy().as('greeting')
mount(<HelloWorld name="Test Aficionado" click={spy} />)
cy.mount(<HelloWorld name="Test Aficionado" click={spy} />)
})

it('shows link', () => {
Expand Down
12 changes: 6 additions & 6 deletions cypress/integration/transpiled-spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Transpiled } from '../../src/transpiled.jsx'
import React from 'react'
import { mount } from '../../lib'

/* eslint-env mocha */
describe('Transpiled', () => {
it('counts clicks', () => {
mount(<Transpiled />)
cy.mount(<Transpiled />)
cy.contains('count: 0')
.click()
.contains('count: 1')
Expand All @@ -14,7 +13,7 @@ describe('Transpiled', () => {
})

it('counts clicks 2', () => {
mount(<Transpiled />)
cy.mount(<Transpiled />)
cy.contains('count: 0')
.click()
.contains('count: 1')
Expand All @@ -23,9 +22,9 @@ describe('Transpiled', () => {
})
})

describe('Counter mounted before each test', () => {
describe('Counter cy.mounted before each test', () => {
beforeEach(() => {
mount(<Transpiled />)
cy.mount(<Transpiled />)
})

it('goes to 3', () => {
Expand All @@ -41,7 +40,8 @@ describe('Counter mounted before each test', () => {
.click()
.click()
.click()
Cypress.component().its('state')
cy.get('@Component')
.its('state')
.should('deep.equal', {count: 3})
})
})
81 changes: 44 additions & 37 deletions cypress/integration/users-spec.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,56 @@
import { Users } from '../../src/users.jsx'
import React from 'react'
import { mount } from '../../lib'

/* eslint-env mocha */
describe('Users', () => {
beforeEach(() => {
mount(<Users />)
context('Users', () => {
describe('Component', () => {
it('fetches 3 users from remote API', () => {
cy.mount(<Users />)
cy.get('li').should('have.length', 3)
})
})

it('fetches 3 users from remote API', () => {
cy.get('li').should('have.length', 3)
})
describe('Network State', () => {
beforeEach(() => {
cy.server()
// cy.mount the component after defining routes in tests
// preventing race conditions where you wait on untouched routes
})

it('can inspect real data in XHR', () => {
cy.server()
cy.route('/users?_limit=3').as('users')
cy.wait('@users').its('response.body').should('have.length', 3)
})

it('can display mock XHR response', () => {
cy.server()
const users = [{id: 1, name: 'foo'}]
cy.route('GET', '/users?_limit=3', users).as('users')
cy.get('li').should('have.length', 1)
.first().contains('foo')
})
it('can inspect real data in XHR', () => {
cy.route('/users?_limit=3').as('users')
cy.mount(<Users />)
cy.wait('@users').its('response.body').should('have.length', 3)
})

it('can inspect mocked XHR', () => {
cy.server()
const users = [{id: 1, name: 'foo'}]
cy.route('GET', '/users?_limit=3', users).as('users')
cy.wait('@users').its('response.body').should('deep.equal', users)
})
it('can display mock XHR response', () => {
const users = [{id: 1, name: 'foo'}]
cy.route('GET', '/users?_limit=3', users).as('users')
cy.mount(<Users />)
cy.get('li').should('have.length', 1)
.first().contains('foo')
})

it('can inspect mocked XHR', () => {
const users = [{id: 1, name: 'foo'}]
cy.route('GET', '/users?_limit=3', users).as('users')
cy.mount(<Users />)
cy.wait('@users').its('response.body').should('deep.equal', users)
})

it('can delay and wait on XHR', () => {
cy.server()
const users = [{id: 1, name: 'foo'}]
cy.route({
method: 'GET',
url: '/users?_limit=3',
response: users,
delay: 1000
}).as('users')
cy.get('li').should('have.length', 0)
cy.wait('@users')
cy.get('li').should('have.length', 1)
it('can delay and wait on XHR', () => {
const users = [{id: 1, name: 'foo'}]
cy.route({
method: 'GET',
url: '/users?_limit=3',
response: users,
delay: 1000
}).as('users')
cy.mount(<Users />)
cy.get('li').should('have.length', 0)
cy.wait('@users')
cy.get('li').should('have.length', 1)
})
})
})
4 changes: 2 additions & 2 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const webpackOptions = {
test: /\.(js|jsx|mjs)$/,
loader: 'babel-loader',
options: {
presets: ['es2015', 'react'],
plugins: ['transform-class-properties'],
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties'],
},
}
]
Expand Down
Loading

0 comments on commit b8ecd2f

Please sign in to comment.