用酶测试简单的 Redux-Form(值在哪里??)
Testing simple Redux-Form with Enzyme (where is value??)
我有一个连接到 redux 示例的最简单的 redux-form :
import * as React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
class TestForm extends React.PureComponent {
render() {
return (
<form>
<Field component={Input} name={'testField'}/>
</form>
);
}
}
class Input extends React.PureComponent {
render(): React.Node {
let { input } = this.props;
// input = {name: 'testField', value: 'test value'};
return (
<input name={input.name} value={input.value} type='text' onChange={()=>1}/>
);
}
}
const mapStateToProps = ({ testForm }) => {
return {
initialValues: testForm,
};
};
export const TestFormWithReduxForm = reduxForm({ form: 'test form'})(TestForm);
export default connect(mapStateToProps)(TestFormWithReduxForm);
注意以下几点:
- 我有自己的自定义输入(称为输入)
- 我正在连接到 reduxForm,然后连接到 redux。
- 传入的初始值应该有'name'和'value'.
我有以下测试(Jest+Enzyme)
import React from 'react';
import { Provider } from 'react-redux';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureStore from 'redux-mock-store';
import TestForm from './TestForm';
Enzyme.configure({ adapter: new Adapter() });
describe('Redux Form Test', () => {
let wrapper;
let mockStoreData = {
testForm: {
testField: 'test value',
},
};
const mockStore = configureStore();
const store = mockStore(mockStoreData);
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<TestForm />
</Provider>
);
});
it('Check Value', () => {
let inputs = wrapper.find('input');
expect(inputs.at(0).prop('name')).toBe('testField'); //OK!!!
expect(inputs.at(0).prop('value')).toBe('test value'); //empty string!!
});
});
Jest 测试将具有 'testField'(及其值)的 'testForm' 对象传递到商店中。
正如预期的那样,第一个输入的名称是 'testField',但是 'value' 是空的(即空字符串)。
这不是预期的,因为如果我在普通页面中呈现组件,那么 'test value' 会出现。
所以这里似乎有什么地方坏了。我不确定它是否与 redux-form 或酶有关,但 redux-form Field 对象似乎正在拦截传递给 Input 对象的属性。
我开始怀疑是否 可能 测试 redux 形式 。
我不确定您为什么想要也不需要测试 Redux Form 的功能,因为它已经被 creators/maintainers 测试过了。但是,redux-mock-store
似乎仅用于 unit
测试(您将模拟 middlewares
并调用 store.dispatch(actionType)
并期望调用 action
)。它不处理 reducer
副作用,也不跟踪 state
中的变化。
在上述情况下,您只需要对自定义 Input
组件进行单元测试,因为这是唯一不同于标准 redux 的组件表格.
也就是说...对于 integration
测试,您需要使用包含 redux-form 的 reducer
和您的 field
的真实 store
状态。
工作示例:https://codesandbox.io/s/zl4p5w26xm(我已经包含了一个 Form
集成测试和一个 Input
单元测试——就像你一样你会注意到,Input
单元测试涵盖了你的大部分测试需求)
containers/Form/Form.js
import React, { Component } from "react";
import { Form, Field, reduxForm } from "redux-form";
import { connect } from "react-redux";
import Input from "../../components/Input/input";
const isRequired = value => (!value ? "Required" : undefined);
class SimpleForm extends Component {
handleFormSubmit = formProps => {
alert(JSON.stringify(formProps, null, 4));
};
render = () => (
<div className="form-container">
<h1 className="title">Text Field</h1>
<hr />
<Form onSubmit={this.props.handleSubmit(this.handleFormSubmit)}>
<Field
className="uk-input"
name="testField"
component={Input}
type="text"
validate={[isRequired]}
/>
<button
type="submit"
className="uk-button uk-button-primary uk-button-large submit"
disabled={this.props.submitting}
>
Submit
</button>
<button
type="button"
className="uk-button uk-button-default uk-button-large reset"
disabled={this.props.pristine || this.props.submitting}
onClick={this.props.reset}
style={{ float: "right" }}
>
Clear
</button>
</Form>
</div>
);
}
export default connect(({ field }) => ({
initialValues: { [field.name]: field.value }
}))(
reduxForm({
form: "SimpleForm"
})(SimpleForm)
);
containers/Form/__test__/Form.js
import React from "react";
import { Provider } from "react-redux";
import { mount } from "enzyme";
import SimpleForm from "../Form";
import store from "../../../store/store";
const wrapper = mount(
<Provider store={store}>
<SimpleForm />
</Provider>
);
describe("Redux Form Test", () => {
it("renders without errors", () => {
expect(wrapper.find(".form-container")).toHaveLength(1);
});
it("fills the input with a default value", () => {
expect(wrapper.find("input").prop("name")).toBe("testField");
expect(wrapper.find("input").prop("value")).toBe("Test Value");
});
it("updates input value when changed", () => {
const event = { target: { value: "Test" } };
wrapper.find("input").simulate("change", event);
expect(wrapper.find("input").prop("value")).toBe("Test");
});
it("resets the input value to defaults when the Clear button has been clicked", () => {
wrapper.find("button.reset").simulate("click");
expect(wrapper.find("input").prop("value")).toBe("Test Value");
});
});
stores/stores.js(为简单起见,我将 reducers
和 store
合并到一个文件中)
import { createStore, combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";
const initialValues = {
name: "testField",
value: "Test Value"
};
const fieldReducer = (state = initialValues, { type, payload }) => {
switch (type) {
default:
return state;
}
};
const reducer = combineReducers({
field: fieldReducer,
form: formReducer
});
export default createStore(reducer);
注意:除了使用 initialValues
,隐藏在文档中,还有其他三种更新字段值的方法:利用 redux-form 的 reducer.plugin 并调度一个动作来更新表单,或者通过将 this.props.intialize({ testField: "Test Value" });
与 enableReinitialize: true
和 keepDirtyOnReinitialize: true,
一起使用,或者通过使用 this.props.change("SimpleForm", { testField: "Test Value" });
。重要的是要注意,因为有时 mapStateToProps
是异步的。
我有一个连接到 redux 示例的最简单的 redux-form :
import * as React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
class TestForm extends React.PureComponent {
render() {
return (
<form>
<Field component={Input} name={'testField'}/>
</form>
);
}
}
class Input extends React.PureComponent {
render(): React.Node {
let { input } = this.props;
// input = {name: 'testField', value: 'test value'};
return (
<input name={input.name} value={input.value} type='text' onChange={()=>1}/>
);
}
}
const mapStateToProps = ({ testForm }) => {
return {
initialValues: testForm,
};
};
export const TestFormWithReduxForm = reduxForm({ form: 'test form'})(TestForm);
export default connect(mapStateToProps)(TestFormWithReduxForm);
注意以下几点:
- 我有自己的自定义输入(称为输入)
- 我正在连接到 reduxForm,然后连接到 redux。
- 传入的初始值应该有'name'和'value'.
我有以下测试(Jest+Enzyme)
import React from 'react';
import { Provider } from 'react-redux';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureStore from 'redux-mock-store';
import TestForm from './TestForm';
Enzyme.configure({ adapter: new Adapter() });
describe('Redux Form Test', () => {
let wrapper;
let mockStoreData = {
testForm: {
testField: 'test value',
},
};
const mockStore = configureStore();
const store = mockStore(mockStoreData);
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<TestForm />
</Provider>
);
});
it('Check Value', () => {
let inputs = wrapper.find('input');
expect(inputs.at(0).prop('name')).toBe('testField'); //OK!!!
expect(inputs.at(0).prop('value')).toBe('test value'); //empty string!!
});
});
Jest 测试将具有 'testField'(及其值)的 'testForm' 对象传递到商店中。
正如预期的那样,第一个输入的名称是 'testField',但是 'value' 是空的(即空字符串)。
这不是预期的,因为如果我在普通页面中呈现组件,那么 'test value' 会出现。
所以这里似乎有什么地方坏了。我不确定它是否与 redux-form 或酶有关,但 redux-form Field 对象似乎正在拦截传递给 Input 对象的属性。
我开始怀疑是否 可能 测试 redux 形式 。
我不确定您为什么想要也不需要测试 Redux Form 的功能,因为它已经被 creators/maintainers 测试过了。但是,redux-mock-store
似乎仅用于 unit
测试(您将模拟 middlewares
并调用 store.dispatch(actionType)
并期望调用 action
)。它不处理 reducer
副作用,也不跟踪 state
中的变化。
在上述情况下,您只需要对自定义 Input
组件进行单元测试,因为这是唯一不同于标准 redux 的组件表格.
也就是说...对于 integration
测试,您需要使用包含 redux-form 的 reducer
和您的 field
的真实 store
状态。
工作示例:https://codesandbox.io/s/zl4p5w26xm(我已经包含了一个 Form
集成测试和一个 Input
单元测试——就像你一样你会注意到,Input
单元测试涵盖了你的大部分测试需求)
containers/Form/Form.js
import React, { Component } from "react";
import { Form, Field, reduxForm } from "redux-form";
import { connect } from "react-redux";
import Input from "../../components/Input/input";
const isRequired = value => (!value ? "Required" : undefined);
class SimpleForm extends Component {
handleFormSubmit = formProps => {
alert(JSON.stringify(formProps, null, 4));
};
render = () => (
<div className="form-container">
<h1 className="title">Text Field</h1>
<hr />
<Form onSubmit={this.props.handleSubmit(this.handleFormSubmit)}>
<Field
className="uk-input"
name="testField"
component={Input}
type="text"
validate={[isRequired]}
/>
<button
type="submit"
className="uk-button uk-button-primary uk-button-large submit"
disabled={this.props.submitting}
>
Submit
</button>
<button
type="button"
className="uk-button uk-button-default uk-button-large reset"
disabled={this.props.pristine || this.props.submitting}
onClick={this.props.reset}
style={{ float: "right" }}
>
Clear
</button>
</Form>
</div>
);
}
export default connect(({ field }) => ({
initialValues: { [field.name]: field.value }
}))(
reduxForm({
form: "SimpleForm"
})(SimpleForm)
);
containers/Form/__test__/Form.js
import React from "react";
import { Provider } from "react-redux";
import { mount } from "enzyme";
import SimpleForm from "../Form";
import store from "../../../store/store";
const wrapper = mount(
<Provider store={store}>
<SimpleForm />
</Provider>
);
describe("Redux Form Test", () => {
it("renders without errors", () => {
expect(wrapper.find(".form-container")).toHaveLength(1);
});
it("fills the input with a default value", () => {
expect(wrapper.find("input").prop("name")).toBe("testField");
expect(wrapper.find("input").prop("value")).toBe("Test Value");
});
it("updates input value when changed", () => {
const event = { target: { value: "Test" } };
wrapper.find("input").simulate("change", event);
expect(wrapper.find("input").prop("value")).toBe("Test");
});
it("resets the input value to defaults when the Clear button has been clicked", () => {
wrapper.find("button.reset").simulate("click");
expect(wrapper.find("input").prop("value")).toBe("Test Value");
});
});
stores/stores.js(为简单起见,我将 reducers
和 store
合并到一个文件中)
import { createStore, combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";
const initialValues = {
name: "testField",
value: "Test Value"
};
const fieldReducer = (state = initialValues, { type, payload }) => {
switch (type) {
default:
return state;
}
};
const reducer = combineReducers({
field: fieldReducer,
form: formReducer
});
export default createStore(reducer);
注意:除了使用 initialValues
,隐藏在文档中,还有其他三种更新字段值的方法:利用 redux-form 的 reducer.plugin 并调度一个动作来更新表单,或者通过将 this.props.intialize({ testField: "Test Value" });
与 enableReinitialize: true
和 keepDirtyOnReinitialize: true,
一起使用,或者通过使用 this.props.change("SimpleForm", { testField: "Test Value" });
。重要的是要注意,因为有时 mapStateToProps
是异步的。