Code Monkey home page Code Monkey logo

redux-form-test's Introduction

Redux-Form Test

This project shows how to do unit and integrations tests with Redux-Form.

For redux-form version 6

This project now uses redux-form version 6. To see test examples for redux-form 5, see this tag: redux-form-5.

Basic premise

Ideally you should do both unit tests and integration tests with your form components.

First, make sure you understand the general idea of testing connected components from the Redux "Writing Tests" doc. Search for the section called Connected Components.

So, you should have a "dumb" (aka presentational) React component that is separate from any connection to Redux and Redux-Form. It takes props. That's it. You test that with unit tests.

You should also have a container React component that connects the presentational component to Redux and Redux-Form. To test this, your test becomes an integration test because you're hooking up your presentational component to redux's store. You're integrating them.

To make this separation clear, I named the presentational component ContactFormComponent, and the container ContactFormContainer, in 2 different files, although you could also set it up so they are in one file.

What should I look at?

This project is very simple. First, run the site (described below) to see what the form looks like. Then view the tests. Take a look at the tests directory and follow the code into the app directory. I recommend you look at the tests/unit directory first, and then tests/integration, because the former is simpler and is a basis for the latter.

The test files are commented with specific pointers.

How to run the site

$ npm install
$ npm run dev

This will run webpack-dev-server, which defaults to port 8080, so you can visit http://localhost:8080. The site is trivial, and exists just to give you a visual of what's happening.

TODO: Fix hot reloading. I installed react-hot-loader 3.0.0-beta.3 because of a warning I received when using the react-hot loader in babel, which said to put react-hot-loader/babel in the plugins section of the .babelrc file, but I haven't done the rest of the wiring yet.

How to run the tests

$ npm install
$ npm run test

Technologies

These tests are written to run with the mocha test framework. I use the chai assertion library to make it more readable; chai-enzyme is also used. But what I'm trying to show off is testing Redux-Form, so of course you could use a different test framework.

Similarly, I'm using Enzyme, which is far better than the basic Facebook React Test Utils. Enzyme is more convenient and intuitive to write, easier to read, and more powerful with great debug helpers. I'm using sinon as a spy library, which is how we know if the functions we pass into our components get called properly. You could exchange sinon for another spy library.

License

MIT

redux-form-test's People

Contributors

tylercollier avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

redux-form-test's Issues

How can we test the onSubmit on a redux-form

I would like to be able to test the onSubmit on a redux-form
I imagine something like this so if there are validations that stop the form form being submitted the spy function passed in the onSubmit = {spy} will not be called. If all the validations are ok . Say for example every Required field is completed, spy.called should be true.

Example

So if i had in my component :

return enzyme.shallow( <Stateless {...requiredProps} {...props} onSubmit={spy} />

Where spy is a const spy = sinon.spy(); And in the component i had this :

  <button
            className={classnames(style.button, style.login, {
                [style.disabled]: invalid || submitting
            })}
            type="submit"
            onClick={this.props.handleSubmit}> 

How can i simulate a click to check if the form is going to be submitted or stopped from being submitted by validators.

If I do this :

    const component = shallow();
    component.find('button').simulate('click'); 

Tests for Enzyme 3

This repo has been very helpful in the past. I wonder if you could update it for react 16, enzyme 3 and redux-form 7?

Uncaught ReferenceError: chai is not defined

I'm trying to implement the way you tests redux-form but there is something wrong when I import sinon-chai module. It seems chai is not imported before sinon-chai use it and I don't figure out how fix it. I know this isn't a redux-form-test bug, but may be you can help me anyway.

Test file

import SignInFormComponent from 'js/components/SignInFormComponent';
import React from 'react';

import chai, { expect } from 'chai';
import sinonChai from 'sinon-chai';
import { shallow } from 'enzyme';
import chaiEnzyme from 'chai-enzyme';
import sinon from 'sinon';

chai.use(sinonChai);
chai.use(chaiEnzyme());

sinon-chai file

// Module systems magic dance.

    /* istanbul ignore else */
    if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
        // NodeJS
        module.exports = sinonChai;
    } else if (typeof define === "function" && define.amd) {
        // AMD
        define(function () {
            return sinonChai;
        });
    } else {
        // Other environment (usually <script> tag): plug in to global chai instance directly.
        chai.use(sinonChai);
    }

Error occurs in chai.use(sinonChai);.

I use Webpack for modules.

Upgrade to show how to test redux-form v6.

Hi nice examples but I was wondering are you planning to add examples on how to test redux-form v6. as I was trying to do it but I am pretty much clueless on how to do this?

Field must be inside a component decorated with reduxForm()

Hello. I try to test checkbox like this

<Field  
  name="my-checkbox"
  id="my-checkbox"
  type="checkbox"
  component="input"
/>

simple test for this checkbox

it("click checkbox", () => {
  _wrapper.find('#my-checkbox').simulate('click');
  expect(_wrapper.find('#my-checkbox')).to.be.checked();
}

and get this error

Error: Field must be inside a component decorated with reduxForm()

I quess the Field after click simulate require redux-form context or smth like this

Testing validation and submit button status

Hi,

I've found your repo here very useful. It's actually brought me back from the edge a few times now so thanks!

I am looking to create some tests that say something like:

  • When I change the value of text field to empty, then I get an error message saying 'There is an error'.

  • When I change the value of text field to empty, then the submit button is disabled.

I'm just wondering what would be the best way to go about this as I can't seem to get much working.

I am using field level validation (https://redux-form.com/7.4.2/examples/fieldlevelvalidation/).

It's so frustrating using this package as there are no details about how to test things and it seems so over-complicated, unfortunately I didn't choose to use it and am stuck working with it.

integration test --> TypeError: Cannot read property 'displayName' of undefined

Hi everyone,

I got a question about the integration test. I have followed your approach but I cannot succeed in mounting nested connected components. I I dont see this error when I run this repo. (maybe it's due to an update of enzyme/mount ?)
After adding a new HOC to my reduxForm, I believe I lose access to the previous one, as seen in this error:

TypeError: Cannot read property 'displayName' of undefined

  at getDisplayName (node_modules/redux-form/lib/util/getDisplayName.js:9:14)
  at node_modules/redux-form/lib/createReduxForm.js:675:65
  at Object.<anonymous> (containers/SignUpContainer.js:6:67)
  at Object.<anonymous> (__test__/SignUp.test.js:10:24)
      at Generator.next (<anonymous>)
      at Promise (<anonymous>)
      at Generator.next (<anonymous>)
      at <anonymous>
  at process._tickCallback (internal/process/next_tick.js:188:7)

here's my component

import TextField from "material-ui/TextField";
import Button from "material-ui/Button";
import Field from "redux-form";

export let renderTextField = ({
  input,
  label,
  meta: { touched, error },
  ...custom
}) => <TextField label={label} placeholder={label} {...input} {...custom} />;

export let SignUp = props => (
  <div>
    <form>
      <Field
        name="username"
        component={renderTextField}
        type="email"
        label="Email"
      />
      <Field
        name="password"
        component={renderTextField}
        type="password"
        label="Password"
      />
      <Button raised onClick={props.handleSubmit(props.signUp)}>
        Register
      </Button>
    </form>
  </div>
);

here's my container

import SignUp from "../components/SignUpComponent";
import { connect } from "react-redux";
import { reduxForm } from "redux-form";
import { reducer } from "../lib/redux_saga";

let SignUpContainer = reduxForm({ form: "formular" })(SignUp);

const mapStatetoProps = null

const mapDispatchToProps = dispatch => {
	return {
		init: () => {
			dispatch(reducer.init());
		},
		signUp: values => {
			dispatch(reducer.signUp(values.username, values.password));
		}
	};
};

const mergeProps = (stateProps, dispatchProps, ownProps) =>
	Object.assign({}, stateProps, dispatchProps, ownProps);

export default connect(mapStatetoProps, mapDispatchToProps, mergeProps)(
	SignUpContainer
); 

here's my test (jest+enzyme)

import { mount, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import SignUpContainer from "../containers/SignUpContainer";
import { Provider } from "react-redux";
import { reducer as formReducer } from "redux-form";
import { createStore, combineReducers } from "redux";
import sinon from 'sinon'

configure({ adapter: new Adapter() });

describe.only("SignUpContainer Mount", () => {
  let store, container, props, init, signUp; 
  beforeEach(() => {
    init = sinon.spy();
    signUp = sinon.spy();
    props = {
      init,
      signUp
    };
    store = createStore(combineReducers({ form: formReducer }));
    container = mount(<Provider store={store}><SignUpContainer {...props} /></Provider>);
  });

  it("render successfully if string is not provided by store", () => {
    const form = container.find("form");
    const input = form.find("input").first();
    input.instance().value = "newusername"
    expect(wrapper.find(TextField).props().value).to.equal('');
    expect(container).toBeDefined();
  });
});

Done with react v16, redux v3.7.2, redux-form v7.1.2, material-ui v1.0.0-beta.20, jest v21.2.1, enzyme v3.1.1

i have tried unsuccessfully shallow(...).dive() as suggested here.

Thanks

Form values on the unit test

Tyler,

Thanks for your awesome example. I was following your example, but there is a case that is missing: how to access the field values on your unit test with shallow renderer?

on my onSubmit handler on my component I'm using:

handleFormSubmit() {
  const field_value = this.props.fields.myfieldname.value;
}

I tried adding my own implementation of the redux-form "onChange" to use enzyme like this:

component.setProps({ fields: { myfield: { value: e.target.value, touched: false, error: '' } })

to set the field value props on the fly when onChange triggers for each field, but still having a hard time setting/getting these props with enzyme as they are not set when pulling them with component.props().prop_name on the test code nor on the component itself cannot access them with: this.props.fields.myfield.value

How would you set the field value props, so that on the component code I could access the values via:

this.props.fields.field_name.value

It would be great to see this in action on your example.

Thanks

Example for integration test- change field value example

I found it extremely hard to change field value in a jest test. I change the entire form looks based on some values, so I investigated and found this:

Setup

"jest": "^23.6.0",
"enzyme": "^3.6.0",
"enzyme-adapter-react-16": "^1.5.0",
"chai": "^4.1.2",
"chai-enzyme": "^1.0.0-beta.1",
............
import chai, { expect } from 'chai'
import chaiEnzyme from 'chai-enzyme'
import { Provider } from 'react-redux'
import { reducer as formReducer } from 'redux-form'
import { createStore, combineReducers } from 'redux'
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });
chai.use(chaiEnzyme())

let store = createStore(combineReducers({ form: formReducer }))

let wrapper = mount(



);
(....)

Test changing field value
.......
const fields = wrapper.find('Field')
let titleInput = fields.findWhere(n => n.prop('name') === 'title')
const input = titleInput.find('input')
input.simulate('change', { target: { value: "sometext" } })
wrapper.update()
expect(store.getState().form.FORMNAME.values.title).to.equal("sometext")

I hope this example may be useful for someone later :)

Unable to test redux-form

Hi Tyler,

Thanks for making these tests available for others to use! I'm have difficultly running both unit and integration tests with the code you've provided. For the sake of brevity, I'll only share some of the code for the integration tests I'm trying to run.

confirmation.js

import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import { sendActivationEmail, resetAuthError } from '../../actions';

export const renderField = ({ input, label, type, meta: { touched, error } }) => (
  <fieldset className="form-group">
    <div className={touched && error ? 'has-danger' : ''}>
      <p>Resend Confirmation Instructions</p>
      <input {...input} placeholder={label} type={type} className="form-control"/>
      {touched && error && <span className="error">{error}</span>}
    </div>
  </fieldset>
)

export class Confirmation extends Component {
  componentWillUnmount() {
    this.props.resetAuthError();
  }

  handleFormSubmit({ email }) {
    this.props.sendActivationEmail({ email });
  }

  renderAlert() {
    if (this.props.errorMessage) {
      return (
        <div className="alert alert-danger">
          <strong>Oops!</strong> {this.props.errorMessage}
        </div>
      )
    }
  }

  render() {
    const { handleSubmit } = this.props;
    return (
      <div>
        {this.renderAlert()}
        <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
          <Field
            label="Email"
            name="email"
            component={renderField}
            type="text"
          />
          <button type="submit" className="btn btn-primary">Resend</button>
        </form>
      </div>
    );
  }
}

function validate(formProps) {
  const errors = {};

  if (!formProps.email) {
    errors.email = 'Please enter an email';
  } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(formProps.email)) {
    errors.email = 'Please enter a valid email address';
  }

  return errors;
}

function mapStateToProps(state) {
  return { errorMessage: state.auth.error }
}

Confirmation = reduxForm({
  form: 'confirmation',
  validate
})(Confirmation);

Confirmation = connect(mapStateToProps, { sendActivationEmail, resetAuthError })(Confirmation);

export default Confirmation;

confirmation_test.js

import { expect } from 'chai';
import { shallow, mount, unmount } from 'enzyme';
import sinon from 'sinon';

import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';

import reducers from '../../../src/reducers';
import ConfirmationContainer, { ConfirmationComponent, renderField }  from '../../../src/components/auth/confirmation';

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);

  describe('Container', () => {
    let sendActivationEmail, resetAuthError, props, errorMessage, subject;
    beforeEach(() => {
      sendActivationEmail = sinon.spy();
      resetAuthError = sinon.spy();
      props = {
        sendActivationEmail,
        resetAuthError,
        errorMessage: 'required'
      };

      subject = mount(
        <Provider store={store}>
          <ConfirmationContainer {...props} />
        </Provider>
        )
      });

    it('renders error message', (done) => {
      expect(subject.find('.alert')).to.have.length(1);
      done();
    });

    it('calls sendActivationEmail on submit', (done)=> {
        const form = subject.find('form');
        const input = subject.find('input').first();

        input.simulate('change', { target: { value: '[email protected]' } });
    	form.simulate('submit');
    	expect(sendActivationEmail.callCount).to.equal(1);
        done();
    });

    it('calls resetAuthError on unmount', (done) => {
        subject.unmount();
        expect(resetAuthError.calledOnce).to.equal(true);
        done();
    });
  });

When I run these tests, I get a bunch of error messages. The component works fine, so I'm thinking there is something wrong with how I've structured the code. Any thoughts on what I'm doing wrong?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.