Skip to content

Commit

Permalink
add mocking defaultProps method example (cypress-io/cypress-react-uni…
Browse files Browse the repository at this point in the history
  • Loading branch information
bahmutov authored Jul 29, 2020
1 parent e94c829 commit daa6281
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Spec | Description
[mock-fetch](cypress/component/advanced/mock-fetch) | Test stubs `window.fetch` used by component in `useEffect` hook
[mocking-axios](cypress/component/advanced/mocking-axios) | Stubbing methods from a 3rd party component like `axios`
[mocking-component](cypress/component/advanced/mocking-component) | Replaced a child component with dummy component during test
[mocking-imports](cypress/component/advanced/mocking-imports) | Stub a named ES6 import using `plugin-transform-modules-commonjs` with `loose: true` when transpiled
[mocking-imports](cypress/component/advanced/mocking-imports) | Stub a named ES6 import in various situations
[react-router-v6](cypress/component/advanced/react-router-v6) | Example testing a [React Router v6](https://github.com/ReactTraining/react-router)
[renderless](cypress/component/advanced/renderless) | Testing a component that does not need to render itself into the DOM
[set-timeout-example](cypress/component/advanced/set-timeout-example) | Control the clock with `cy.tick` and test loading components that use `setTimeout`
Expand Down
30 changes: 30 additions & 0 deletions cypress/component/advanced/mocking-imports/PizzaProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import { fetchIngredients as defaultFetchIngredients } from './services'

export default function PizzaProps({ fetchIngredients }) {
const [ingredients, setIngredients] = React.useState([])

const handleCook = () => {
fetchIngredients().then(response => {
setIngredients(response.args.ingredients)
})
}

return (
<>
<h3>Pizza</h3>
<button onClick={handleCook}>Cook</button>
{ingredients.length > 0 && (
<ul>
{ingredients.map(ingredient => (
<li key={ingredient}>{ingredient}</li>
))}
</ul>
)}
</>
)
}

PizzaProps.defaultProps = {
fetchIngredients: defaultFetchIngredients,
}
21 changes: 21 additions & 0 deletions cypress/component/advanced/mocking-imports/PizzaProps.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import PizzaProps from './PizzaProps'
import { mount } from 'cypress-react-unit-test'

const ingredients = ['bacon', 'tomato', 'mozzarella', 'pineapples']

describe('PizzaProps', () => {
it('mocks method in the default props', () => {
cy.stub(PizzaProps.defaultProps, 'fetchIngredients')
.resolves({ args: { ingredients } })
.as('fetchMock')
mount(<PizzaProps />)
cy.contains('button', /cook/i).click()

for (const ingredient of ingredients) {
cy.contains(ingredient)
}

cy.get('@fetchMock').should('have.been.calledOnce')
})
})
30 changes: 27 additions & 3 deletions cypress/component/advanced/mocking-imports/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ In babel configuration file, add one more plugin
```js
// https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs
// loose ES6 modules allow us to dynamically mock imports during tests
['@babel/plugin-transform-modules-commonjs', {
loose: true
}]
;[
'@babel/plugin-transform-modules-commonjs',
{
loose: true,
},
]
```

The ES6 exports and imports then will be a plain object then
Expand Down Expand Up @@ -44,3 +47,24 @@ it('shows mock greeting', () => {
cy.contains('h1', 'test greeting').should('be.visible')
})
```

## PizzaProps

If the component is using `defaultProps` to pass a method to call, you can stub it, see [PizzaProps.js](PizzaProps.js) and [PizzaProps.spec.js](PizzaProps.spec.js)

```js
import PizzaProps from './PizzaProps'
cy.stub(PizzaProps.defaultProps, 'fetchIngredients').resolves(...)
```

## RemotePizza

Even if the import is renamed, you can stub using the original name, see [RemotePizza.js](RemotePizza.js) and [RemotePizza.spec.js](RemotePizza.spec.js)

```js
// RemotePizza.js
import { fetchIngredients as defaultFetchIngredients } from './services'
// RemotePizza.spec.js
import * as services from './services'
cy.stub(services, 'fetchIngredients').resolves(...)
```
26 changes: 26 additions & 0 deletions cypress/component/advanced/mocking-imports/RemotePizza.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { fetchIngredients as defaultFetchIngredients } from './services'

export default function RemotePizza() {
const [ingredients, setIngredients] = React.useState([])

const handleCook = () => {
defaultFetchIngredients().then(response => {
setIngredients(response.args.ingredients)
})
}

return (
<>
<h3>Pizza</h3>
<button onClick={handleCook}>Cook</button>
{ingredients.length > 0 && (
<ul>
{ingredients.map(ingredient => (
<li key={ingredient}>{ingredient}</li>
))}
</ul>
)}
</>
)
}
23 changes: 23 additions & 0 deletions cypress/component/advanced/mocking-imports/RemotePizza.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react'
import RemotePizza from './RemotePizza'
import { mount } from 'cypress-react-unit-test'
// prepare for import mocking
import * as services from './services'

const ingredients = ['bacon', 'tomato', 'mozzarella', 'pineapples']

describe('RemotePizza', () => {
it('mocks named import from services', () => {
cy.stub(services, 'fetchIngredients')
.resolves({ args: { ingredients } })
.as('fetchMock')
mount(<RemotePizza />)
cy.contains('button', /cook/i).click()

for (const ingredient of ingredients) {
cy.contains(ingredient)
}

cy.get('@fetchMock').should('have.been.calledOnce')
})
})
4 changes: 4 additions & 0 deletions cypress/component/advanced/mocking-imports/services.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const fetchIngredients = () =>
fetch(
'https://httpbin.org/anything?ingredients=bacon&ingredients=mozzarella&ingredients=pineapples',
).then(r => r.json())
36 changes: 19 additions & 17 deletions cypress/component/advanced/mocking-imports/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ import { mount } from 'cypress-react-unit-test'
import Component from './component'
import * as GreetingModule from './greeting'

it('shows real greeting', () => {
mount(<Component />)
cy.contains('h1', 'real greeting').should('be.visible')
})
describe('Mocking ES6 import', () => {
it('shows real greeting', () => {
mount(<Component />)
cy.contains('h1', 'real greeting').should('be.visible')
})

it('shows mock greeting', () => {
// stubbing ES6 named imports works via
// @babel/plugin-transform-modules-commonjs with "loose: true"
// because the generated properties are configurable
it('shows mock greeting', () => {
// stubbing ES6 named imports works via
// @babel/plugin-transform-modules-commonjs with "loose: true"
// because the generated properties are configurable

// stub property on the loaded ES6 module using cy.stub
// which will be restored after the test automatically
cy.stub(GreetingModule, 'greeting', 'test greeting')
mount(<Component />)
cy.contains('h1', 'test greeting').should('be.visible')
})
// stub property on the loaded ES6 module using cy.stub
// which will be restored after the test automatically
cy.stub(GreetingModule, 'greeting', 'test greeting')
mount(<Component />)
cy.contains('h1', 'test greeting').should('be.visible')
})

it('shows real greeting again', () => {
mount(<Component />)
cy.contains('h1', 'real greeting').should('be.visible')
it('shows real greeting again', () => {
mount(<Component />)
cy.contains('h1', 'real greeting').should('be.visible')
})
})
1 change: 1 addition & 0 deletions examples/react-scripts/cypress.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"viewportWidth": 500,
"viewportHeight": 800,
"experimentalComponentTesting": true,
"experimentalFetchPolyfill": true,
"componentFolder": "src"
}
6 changes: 3 additions & 3 deletions examples/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"private": true,
"scripts": {
"start": "../../node_modules/.bin/react-scripts start",
"test": "node ../../scripts/cypress-expect run --passing 10",
"test": "node ../../scripts/cypress-expect run --passing 13",
"cy:open": "../../node_modules/.bin/cypress open",
"check-coverage": "../../node_modules/.bin/check-coverage src/App.js src/calc.js src/Child.js cypress/fixtures/add.js",
"only-covered": "../../node_modules/.bin/only-covered src/App.js src/calc.js src/Child.js cypress/fixtures/add.js"
"check-coverage": "../../node_modules/.bin/check-coverage src/App.js src/calc.js src/Child.js src/services.js src/RemotePizza.js cypress/fixtures/add.js",
"only-covered": "../../node_modules/.bin/only-covered src/App.js src/calc.js src/Child.js src/services.js src/RemotePizza.js cypress/fixtures/add.js"
},
"devDependencies": {
"cypress-react-unit-test": "file:../.."
Expand Down
44 changes: 44 additions & 0 deletions examples/react-scripts/src/RemotePizza.cy-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'
import RemotePizza from './RemotePizza'
import { mount } from 'cypress-react-unit-test'

const ingredients = ['bacon', 'tomato', 'mozzarella', 'pineapples']

describe('RemotePizza', () => {
it('download ingredients from internets (network mock)', () => {
cy.server()
cy.route('https://httpbin.org/anything*', { args: { ingredients } }).as(
'pizza',
)

mount(<RemotePizza />)
cy.contains('button', /cook/i).click()
cy.wait('@pizza') // make sure the network stub was used

for (const ingredient of ingredients) {
cy.contains(ingredient)
}
})

it('stubs via prop (di)', () => {
const fetchIngredients = cy.stub().resolves({ args: { ingredients } })
mount(<RemotePizza fetchIngredients={fetchIngredients} />)
cy.contains('button', /cook/i).click()

for (const ingredient of ingredients) {
cy.contains(ingredient)
}
})

it('mocks default props method', () => {
cy.stub(RemotePizza.defaultProps, 'fetchIngredients').resolves({
args: { ingredients },
})
mount(<RemotePizza />)
cy.contains('button', /cook/i).click()

for (const ingredient of ingredients) {
cy.contains(ingredient)
}
})
})
30 changes: 30 additions & 0 deletions examples/react-scripts/src/RemotePizza.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import { fetchIngredients as defaultFetchIngredients } from './services'

export default function RemotePizza({ fetchIngredients }) {
const [ingredients, setIngredients] = React.useState([])

const handleCook = () => {
fetchIngredients().then(response => {
setIngredients(response.args.ingredients)
})
}

return (
<>
<h3>Pizza</h3>
<button onClick={handleCook}>Cook</button>
{ingredients.length > 0 && (
<ul>
{ingredients.map(ingredient => (
<li key={ingredient}>{ingredient}</li>
))}
</ul>
)}
</>
)
}

RemotePizza.defaultProps = {
fetchIngredients: defaultFetchIngredients,
}
4 changes: 4 additions & 0 deletions examples/react-scripts/src/services.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const fetchIngredients = () =>
fetch(
'https://httpbin.org/anything?ingredients=bacon&ingredients=mozzarella&ingredients=pineapples',
).then(r => r.json())

0 comments on commit daa6281

Please sign in to comment.