Search code examples
unit-testingreactjssnapshotjestjsredux-form

How to stop redux-form or React from changing htmlFor and id when creating Jest snapshots?


I've got a wizard form made with redux-forms v6 and it looks something like:

--

index.js - Holds page number in local state, is connected to application level state

PageOne - wrapped with reduxForm decorator (form: 'wizForm')

PageTwo - wrapped with reduxForm decorator (form: 'wizForm')

--

PageOne and PageTwo both contain additional components that render sections of the form (initial fields, vehicle information, driver information...), and each of those sections render their own components for each question in that section.

Since there's a lot of nested components and I want to test that PageOne and PageTwo call the props passed from index.js, I've resorted to using Enzyme's mount() function with a fake store. I want to MatchSnapshot() with Jest to compare whether index.js is rendering PageOne or PageTwo, after certain buttons are clicked to go back and forth from pages.

The problem is when I do create snapshots, other than creating a 16,000 line snapshot, the snapshot will NEVER match the previous one even if I don't change anything. I'm not sure if it's redux-form that's doing it or React, but the htmlFor and the id keep changing between snapshots, test after test after test.

We use css-modules too, but I don't think that's causing the problem, and we did configure Jest to work with css-modules too, modifying "moduleNameWrapper" to mock .css files. Does anyone know how to fix this or where I should look?

tests:

describe('<VehicleAddition />', () => {
  let props;
  beforeEach(() => {
    props = {
      ...,
    };
  });

it('Renders initially', () => {
    const component = shallow(<VehicleAddition {...props} />);
    expect(toJson(component)).toMatchSnapshot();
  });

  it('Renders <PageTwo> when <PageOne> form is submitted', () => {
    const component = shallow(<VehicleAddition {...props} />);
    expect(toJson(component)).toMatchSnapshot();
    component.find('ReduxForm') // reduxForm HOC wraps the <form> in a <ReduxForm> component
      .first()
      .simulate('submit');
    expect(toJson(component)).toMatchSnapshot();
    expect(component.state().page).toEqual(2);
  });

  it('PageTwoStuffs', () => {
    // Render the form, click 'next', assert it's page two
    // click 'previous'
    jest.enableAutomock();
    const store = createStore(
      combineReducers({
        route: jest.fn(() => Immutable.fromJS({})),
        language: jest.fn(() => Immutable.fromJS({})),
        global: jest.fn(() => Immutable.fromJS({})),
        form: formReducer,
      }),
      Immutable.fromJS({}),
    );
    const component = mount(
      <Provider store={store}>
        <VehicleAddition {...props} />
      </Provider>
    );

    // CAN'T check the state of <VehicleAddition /> because it can only be done on root component, says the error message.
    expect(toJson(component)).toMatchSnapshot();

index.js:

export class VehicleAddition extends React.Component { // eslint-disable-line
  constructor(props) {
    super(props);
    this.state = {
      page: 1,
    };
  }

nextPage = () => {
    this.setState({ page: this.state.page + 1 });
  }

previousPage = () => {
    this.setState({ page: this.state.page - 1 });
}


render() {
    return (
      <div>
        {page === 1 &&
          <PageOne
            {...this.props}
          />
        }
        {page === 2 &&
          <PageTwo
            {...this.props}
          />
        }
      </div>
    );
  }
}

PageOne.js

class PageOne extends React.Component { // eslint-disable-line

render() {
    const {
       ...
    } = this.props;
    return (
      <form onSubmit={handleSubmit}>
        <div>
          <InitialFields
            autoPolicies={autoPolicies}
            changeField={this.changeField}
            getFormValues={getFormValues}
            policies={policies}
            primary={primary}
          />
          <VehicleBeingAddedFields
            changeField={this.changeField}
            getFormValues={getFormValues}
            fetchVehMakes={fetchVehMakes}
            fetchVehModels={fetchVehModels}
            policies={policies}
            vehMakes={vehMakes}
            vehModels={vehModels}
          />
...
    <div className="btn-group btn-group-float-right">
          <button
            type="submit"
            onClick={this.handleClick}
            disabled={pristine || submitting}
            className="btn-primary"
          >
            Next
          </button>
        </div>
      </form>
    );
  }
}

PageTwo.js:

class PageTwo extends React.Component { // eslint-disable-line
  render() {
    const {
      ...
    } = this.props;
    return (
      <form onSubmit={handleSubmit}>
        ...
        <div className="btn-group btn-group-float-right">
          <button type="button" className="btn" onClick={previousPage}>Previous</button>{' '}
          <button type="submit" disabled={pristine || submitting} className="btn-primary">Submit</button>
        </div>
      </form>
    );
  }
}

Example of the parts of the snapshot that constantly changes:

enter image description here

enter image description here


Solution

  • I solved it by passing a hardcoded id value from the test cases

    import React from 'react';
    import renderer from 'react-test-renderer';
    import { reduxForm } from 'redux-form';
    import { createStore } from 'redux';
    import { Provider } from 'react-redux';
    import { mount } from 'enzyme'
    import TodoItem from './TodoItem';
    import injectTapEventPlugin from 'react-tap-event-plugin';
    
    function setup() {
        const spy = jest.fn();
        const store = createStore(() => ({}));
    
        const Decorated = reduxForm({ form: 'testForm' })(TodoItem);
        const props = {
            remove: jest.fn(),
            TodoItemReduxFormInitialName: "fullName",
            snapshotTestId:"4"
        }
        const mockedComponent = <Provider store={store}>
                                    <Decorated {...props} />
                                </Provider>;
    
        const enzymeWrapper = mount(mockedComponent)
    
        injectTapEventPlugin();
    
        return {
            props,
            mockedComponent,
            enzymeWrapper
        }
    }
    describe('TodoItem Component', () => {
        it('should render the snapshot', () => {
            const {mockedComponent} = setup()
            const tree = renderer.create(
                mockedComponent
            ).toJSON();
            expect(tree).toMatchSnapshot();
        });
    
    
        //not required as snapshot testing covers it
        it('should render Number', () => {
            const {enzymeWrapper} = setup()
            const fieldProps = enzymeWrapper.find('Field').at(0).props();
            expect(fieldProps.hintText).toEqual('Item Number');
            expect(fieldProps.name).toEqual('fullName.itemNumber');
        });
    
    
        //not required as snapshot testing covers it
        it('should render remove button', () => {
            const {enzymeWrapper} = setup()
            const button = enzymeWrapper.find('RaisedButton').at(0).props();
            expect(button.label).toEqual("remove")
        });
    });