Skip to content

Instantly share code, notes, and snippets.

@the0neWhoKnocks
Last active February 22, 2019 19:39
Show Gist options
  • Save the0neWhoKnocks/55a2bf0d5d382351eb1273feaaab78dc to your computer and use it in GitHub Desktop.
Save the0neWhoKnocks/55a2bf0d5d382351eb1273feaaab78dc to your computer and use it in GitHub Desktop.
Enzyme Testing

Enzyme Testing

http://airbnb.io/enzyme/docs/api/


How to test a nested component when using shallow

There are cases, where you may have a connected component, or you've had to wrap a component for one reason or another. Using shallow instead of mount has less overhead, but you can't use find since it's only one level deep. That's where dive comes in. Imagine you have a component like this

import React from 'react';
import ComponentA from './ComponentA';

const WrappedComponent = (props) => {
  return (props.retComp) ? <ComponentA {...props} /> : null;
};

export default WrappedComponent;

And in your test you want to ensure that any custom props have propagated to ComponentA.

it('should propagate props to wrapped component', () => {
  const fu = 'bar';
  const wrapper = shallow(<WrappedComponent fu={fu} />);
  const comp = wrapper.dive(); // go from `WrappedComponent` to `ComponentA`
  const compProps = comp.props();

  expect(cS.name()).toBe('ComponentA'); // ensure you're testing the correct component
  expect(currProps.fu).toBe(fu);
});

How to test components that use 'createRef' or 'forwardRef'?

For components that use forwardRef you'll want to treat them like connected components and export the forwarded item as the default, and the plain component as a named export.

class MyComponent extends Component {}
MyComponent.displayName = 'MyComponent';

const Forwarded = forwardRef((props, ref) => <MyComponent {...props} refProp={ref} />);
// It's important to set the `displayName` so that you can easily targed the forwarded component in tests.
Forwarded.displayName = 'ForwardedMyComponent';
export default Forwarded;

// OR this if `refs` is an Object
const Forwarded = forwardRef((props, refs) => <MyComponent {...props} {...refs} />);

export {
  MyComponent,
};

For components that call createRef you'll have to mock out createRef in your test.

import React, { createRef, forwardRef } from 'react';

jest.mock('react', () => {
  const actualModule = jest.requireActual('react');
  return {
    ...actualModule,
    createRef: jest.fn(),
  };
});

describe('Test', () => {
  const Comp = forwardRef((props, ref) => <div className="child" {...props} namedRef={ref} />);
  const CompWithMultipleRefs = forwardRef((props, refs) => <div className="child" {...props} {...refs} />);
  let props, ref, ref1, ref2;
  
  beforeEach(() => {
    props = { fu: 'bar' };
    // You can mock the same value for all calls.
    ref = { current: {} };
    createRef.mockReturnValue(ref);
    
    // OR if you have multiple ordered calls to createRef, you could return a specific instance for every call.
    ref1 = { current: {} };
    ref2 = { current: {} };
    createRef.mockReturnValueOnce(ref1);
    createRef.mockReturnValueOnce(ref2);
  });
  
  describe('func', () => {
    it('should forward ref', () => {
      const wrapper = shallow(<Comp {...props} ref={ref} />);
      expect(wrapper.find('.child').props().namedRef).toEqual(ref);
    });
  });
});

Why Isn't the Wrapper Updating the Markup?

Imagine a scenario where you've triggered a function on your component that's triggered a state change and a render. You should be able to call wrapper.find('Component.new-class) (if new-class is added on state change). However in some cases you won't get the expected results, so you throw a console.log(wrapper.debug()) in, and sure enough you're class wasn't added. That's where wrapper.update() comes in. Calling that method updates the wrapper reference and you should now get the expected results.


How to Evaluate State When a Function Calls setState Internally

In most cases you can use wrapper.update() which will wait for the instance's state to be updated, then you execute your assetions. If this fails, continue reading.

There will be some cases where a function calls setState internally and there's no state callback to add your assertions to. In those cases you'll need to use process.nextTick.

NOTE: If there are any uncaught errors within the process.nextTick call, Jest will completely exit.

it('should do something', (done) => {
  instance.setState({
    toggled: true,
  }, () => {
    instance.someCallBackThatCallsSetState();

    process.nextTick(() => {
      expect(instance.state).toEqual(expect.objectContaining({
        // some props
      }));

      done();
    });
  });
});

How to simulate a click on a bound instance method?

Example Class

class Component {
  constructor() {
    this.someMethod = this.someMethod.bind(this);
  }
  
  someMethod() {}
}

How to mock and test

jest.spyOn(Component.prototype, 'someMethod');
Component.prototype.someMethod.mockImplementation(jest.fn());

// render your component here via `mount` or `shallow`

wrapper.find('Something').simulate('click');
expect(Component.prototype.someMethod).toHaveBeenCalled();

// important to restore the function otherwise it could bleed over into other tests.
Component.prototype.someMethod.mockRestore();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment