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

add mocking defaultProps method example #359

Merged
merged 4 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())