Skip to content
This repository was archived by the owner on Mar 5, 2022. It is now read-only.
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Stubbing clock | ✅ | ✅
Code coverage | ✅ | ✅
<!-- prettier-ignore-end -->

If you are coming from Jest + RTL world, read [Test The Interface Not The Implementation](https://glebbahmutov.com/blog/test-the-interface/).

If you are coming from Enzyme world, check out the [enzyme](cypress/component/basic/enzyme) example.

## Blog posts

- [My Vision for Component Tests in Cypress](https://glebbahmutov.com/blog/my-vision-for-component-tests/)
Expand Down Expand Up @@ -126,6 +130,8 @@ Spec | Description
[alert-spec.js](cypress/component/basic/alert-spec.js) | Component tries to use `window.alert`
[counter-set-state](cypress/component/basic/counter-set-state) | Counter component that uses `this.state`
[counter-use-hooks](cypress/component/basic/counter-use-hooks) | Counter component that uses `useState` hook
[document-spec](cypress/component/basic/document) | Checks `document` dimensions from the component
[enzyme](cypress/component/basic/enzyme) | Several specs showing how to recreate Enzyme's `setProps`, `setState`, and `setContext` methods.
[emotion-spec.js](cypress/component/basic/emotion-spec.js) | Confirms the component is using `@emotion/core` and styles are set
[error-boundary-spec.js](cypress/component/basic/error-boundary-spec.js) | Checks if an error boundary component works
[pure-component-spec.js](cypress/component/basic/pure-component.spec.js) | Tests stateless component
Expand All @@ -142,7 +148,6 @@ Spec | Description
[typescript](cypress/component/basic/typescript) | A spec written in TypeScript
[unmount](cypress/component/basic/unmount) | Verifies the component's behavior when it is unmounted from the DOM
[use-lodash-fp](cypress/component/basic/use-lodash-fp) | Imports and tests methods from `lodash/fp` dependency
[document-spec](cypress/component/basic/document) | Checks `document` dimensions from the component
[styled-components](cypress/component/basic/styled-components) | Test components that use [styled-components](https://www.styled-components.com/)
<!-- prettier-ignore-end -->

Expand Down
98 changes: 98 additions & 0 deletions cypress/component/basic/enzyme/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Enzyme examples

This folder shows several examples from [Enzyme docs](https://enzymejs.github.io/enzyme/).

In general if you are migrating from Enzyme to `cypress-react-unit-test`:

- there is no shallow mounting, only the full mounting. Thus `cypress-react-unit-test` has `mount` which is similar to the Enzyme's `render`. It renders the full HTML and CSS output of your component.
- you can mock [children components](https://github.com/bahmutov/cypress-react-unit-test/tree/main/cypress/component/advanced/mocking-component) if you want to avoid running "expensive" components during tests
- the test is running as a "mini" web application. Thus if you want to set a context around component, then set the [context around the component](https://github.com/bahmutov/cypress-react-unit-test/tree/main/cypress/component/advanced/context)

## setState

If you want to change the component's internal state, use the component reference. You can get it by using the special property `ref` when mounting.

```js
// get the component reference using "ref" prop
// and place it into the object for Cypress to "wait" for it
let c = {}
mount(<Foo id="foo" foo="initial" ref={i => (c.instance = i)} />)
cy.wrap(c)
.its('instance')
.invoke('setState', { count: 10 })
```

See [state-spec.js](state-spec.js) file.

## setProps

There is no direct implementation of `setProps`. If you want to see how the component behaves with different props:

```js
it('mounts component with new props', () => {
mount(<Foo id="foo" foo="initial" />)
cy.contains('initial').should('be.visible')

mount(<Foo id="foo" foo="second" />)
cy.contains('second').should('be.visible')
})
```

If you want to reuse properties, you can even clone the component

```js
it('mounts cloned component', () => {
const cmp = <Foo id="foo" foo="initial" />
mount(cmp)
cy.contains('initial').should('be.visible')

const cloned = Cypress._.cloneDeep(cmp)
// change a property, leaving the rest unchanged
cloned.props.foo = 'second'
mount(cloned)
cy.contains('.foo', 'second').should('be.visible')
})
```

See [props-spec.js](props-spec.js) file.

## context

Enzyme's `mount` method allows passing the [React context](https://reactjs.org/docs/context.html) as the second argument to the JSX component like `SimpleComponent` below.

```js
function SimpleComponent(props, context) {
const { name } = context
return <div>{name || 'not set'}</div>
}
```

Since the above syntax is [deprecated](https://reactjs.org/docs/legacy-context.html), `cypress-react-unit-test` does not support it. Instead use `createContext` and `Context.Provider` to surround the mounted component, just like you would do in a regular application code.

```js
mount(
<SimpleContext.Provider value={{ name: 'test context' }}>
<SimpleComponent />
</SimpleContext.Provider>,
)
```

Instead of setting a new context, mount the same component but surround it with a different context provider

```js
const cmp = <SimpleComponent id="0x123" />
mount(
<SimpleContext.Provider value={{ name: 'first context' }}>
{cmp}
</SimpleContext.Provider>,
)

// same component, different provider
mount(
<SimpleContext.Provider value={{ name: 'second context' }}>
{cmp}
</SimpleContext.Provider>,
)
```

See [context-spec.js](context-spec.js) for more examples.
50 changes: 50 additions & 0 deletions cypress/component/basic/enzyme/context-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'
import { SimpleContext } from './simple-context'
import { SimpleComponent } from './simple-component.jsx'

// testing components that use Context React API
// https://reactjs.org/docs/context.html
describe('Enzyme', () => {
context('setContext', () => {
it('does not provide the context', () => {
mount(<SimpleComponent />)
cy.contains('context not set').should('be.visible')
})

it('provides the context', () => {
// surround the component with the real provider but
// set the value prop to whatever the test requires
mount(
<SimpleContext.Provider value={{ name: 'test context' }}>
<SimpleComponent />
</SimpleContext.Provider>,
)
cy.contains('test context').should('be.visible')
})

it('mounts new context', () => {
// instead of setting the context from the test
// just mount the component again with a different provider around it
const cmp = <SimpleComponent id="0x123" />

mount(
<SimpleContext.Provider value={{ name: 'first context' }}>
{cmp}
</SimpleContext.Provider>,
)
cy.contains('first context').should('be.visible')
cy.contains('.id', '0x123').should('be.visible')

// same component, different provider
mount(
<SimpleContext.Provider value={{ name: 'second context' }}>
{cmp}
</SimpleContext.Provider>,
)
cy.contains('second context').should('be.visible')
cy.contains('.id', '0x123').should('be.visible')
})
})
})
77 changes: 77 additions & 0 deletions cypress/component/basic/enzyme/props-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'

class Foo extends React.Component {
constructor(props) {
super(props)

this.state = {
count: 0,
}
}

componentDidMount() {
console.log('componentDidMount called')
}

componentDidUpdate() {
console.log('componentDidUpdate called')
}

render() {
const { id, foo } = this.props
return (
<div className={id}>
{foo} count {this.state.count}
</div>
)
}
}

describe('Enzyme', () => {
// example test copied from
// https://github.com/enzymejs/enzyme/blob/master/packages/enzyme-test-suite/test/shared/methods/setProps.jsx

context('setProps', () => {
it('gets props from the component', () => {
mount(<Foo id="foo" foo="initial" />)
cy.contains('initial').should('be.visible')

cy.get('@Foo')
.its('props')
.then(props => {
console.log('current props', props)
expect(props).to.deep.equal({
id: 'foo',
foo: 'initial',
})
// you can get current props of the component
// but not change them - they are read-only
expect(() => {
props.foo = 'change 1'
}).to.throw()
})
})

it('mounts component with new props', () => {
mount(<Foo id="foo" foo="initial" />)
cy.contains('initial').should('be.visible')

mount(<Foo id="foo" foo="second" />)
cy.contains('second').should('be.visible')
})

it('mounts cloned component', () => {
const cmp = <Foo id="foo" foo="initial" />
mount(cmp)
cy.contains('initial').should('be.visible')

const cloned = Cypress._.cloneDeep(cmp)
// change a property, leaving the rest unchanged
cloned.props.foo = 'second'
mount(cloned)
cy.contains('.foo', 'second').should('be.visible')
})
})
})
23 changes: 23 additions & 0 deletions cypress/component/basic/enzyme/simple-component.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react'
import { SimpleContext } from './simple-context'

export class SimpleComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
id: props.id || 'unknown id',
}
}

render() {
console.log('context %o', this.context)
return (
<>
<div>{this.context.name || 'context not set'}</div>
<div className="id">{this.state.id}</div>
</>
)
}
}

SimpleComponent.contextType = SimpleContext
3 changes: 3 additions & 0 deletions cypress/component/basic/enzyme/simple-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// https://reactjs.org/docs/context.html
import { createContext } from 'react'
export const SimpleContext = createContext({ name: '' })
56 changes: 56 additions & 0 deletions cypress/component/basic/enzyme/state-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'

class Foo extends React.Component {
constructor(props) {
super(props)

this.state = {
count: 0,
}
}

componentDidMount() {
console.log('componentDidMount called')
}

componentDidUpdate() {
console.log('componentDidUpdate called')
}

render() {
const { id, foo } = this.props
return (
<div className={id}>
{foo} count {this.state.count}
</div>
)
}
}

describe('Enzyme', () => {
context('setState', () => {
it('sets component state', () => {
// get the component reference using "ref" prop
// and place it into the object for Cypress to "wait" for it
let c = {}
mount(<Foo id="foo" foo="initial" ref={i => (c.instance = i)} />)
cy.contains('initial').should('be.visible')

cy.log('**check state**')
cy.wrap(c)
.its('instance.state')
.should('deep.equal', { count: 0 })

cy.log('**setState**')
cy.wrap(c)
.its('instance')
.invoke('setState', { count: 10 })
cy.wrap(c)
.its('instance.state')
.should('deep.equal', { count: 10 })
cy.contains('initial count 10')
})
})
})