Skip to content

Instantly share code, notes, and snippets.

@JesterXL
Last active April 9, 2018 12:01
Show Gist options
  • Save JesterXL/3def2d5fbcd32f6df225493d5ec889c2 to your computer and use it in GitHub Desktop.
Save JesterXL/3def2d5fbcd32f6df225493d5ec889c2 to your computer and use it in GitHub Desktop.

Side Effects

A few of the Enzyme examples show using a class method of a component, but instead of using the instance() function to get the class instance and test the method's return value, instead they simulate events, then check the side effects of those events called from the method you want to test.

In the below example, unit test coverage will say the Failure part of the onNameBlur method was covered.

AddUser.js

class AddUserForm extends React.Component {
  ...
  onNameBlur = event => {
        const name = event.target.value
        validName(name).matchWith({
            Failure: ({value}) => this.setState({name, nameErrors: value.join(' '), clean: false}),
            Success: () => this.setState({name, nameErrors: undefined, clean: false})
        })
    }
  ...
  <TextField hintText="Your Full Name" onBlur={this.onNameBlur} />
    ...
}

AddUser.test.js

it('should work', ()=> {
    const addUserForm = shallow(<AddUserForm />)
    addUserForm.find('[hintText="Your Full Name"]').simulate('blur', {target: {value: ' '}})
    expect(addUserForm.state('nameErrors')).toEqual('Invalid empty name, cannot be a blank string, how about J instead?')
 })

Pure

In a pure way, you instead simply write functions, and bind to them in your JSX. What is NOT shown here is you still should do the above test to verify once you integrate them, they do in fact work. My friend Steven Sacks has pointed out I could simply use the Enyzme instance() function to call the methods directly, but you'd still have to be ok with the state and this keyword. My FP spidey sense hates it, but my pragmattic get things done + still have 100% coverage is ok with it.

AddUser.js

export const onNameBlur = curryN(2, (self, event) => {
    const name = event.target.value
    return validName(name).matchWith({
        Failure: ({value}) => self.setState({name, nameErrors: value.join(' '), clean: false}),
        Success: () => self.setState({name, nameErrors: undefined, clean: false})
    })
})
...
<TextField hintText="Your Full Name" onBlur={onNameBlur(this)} />
...

AddUser.test.js

it('should work', ()=> {
  const result = onNameBlur({ setState: identity }, {target: {value: ' '}})
  expect(result.nameErrors).toEqual('Invalid empty name, cannot be a blank string, how about J instead?')
})

Pure Class

If you're not down using closures or curried functions and just want a React class and you're fine with using instance(), then ensure the function at least returns a value.

AddUser.js

onNameBlur = event => {
    const name = event.target.value
    return validName(name).matchWith({
        Failure: ({value}) => {
            const failedState = {name, nameErrors: value.join(' '), clean: false}
            this.setState(failedState)
            return failedState
        },
        Success: () => {
            const successState = {name, nameErrors: undefined, clean: false}
            this.setState(successState)
            return successState
        }
    })
}

AddUser.test.js

it('should work', ()=> {
  const addUserForm = shallow(<AddUserForm />)
  const result = addUserForm.instance().onNameBlur({target: {value: ' '}})
  expect(result.nameErrors).toEqual('Invalid empty name, cannot be a blank string, how about J instead?')
})

You'll notice in the example that you have to do a lot of verbose coding to compensate for setState not returning a value. With a simple helper method:

const setState = (self, o) => {
    self.setState(o)
    return o
}

You can then modify it so when you start doing this a lot, your code stays more concise:

onNameBlur = event => {
        const name = event.target.value
        return validName(name).matchWith({
            Failure: ({value}) => setState(this, {name, nameErrors: value.join(' '), clean: false}),
            Success: () => setState(this, {name, nameErrors: undefined, clean: false})
        })
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment