Jest how to check spy function being called within a spy function? - reactjs

Consider the following react component.
import React, { Component } from "react";
import { reduxForm, Field } from "redux-form";
import { connect } from "react-redux";
import formFields from "components/Login/formFields";
import LoginField from "components/Login/LoginField/LoginField";
import _ from "lodash";
import { loginFormSubmit } from "store/actions/profile/profile";
import { SubmissionError } from "redux-form";
export class Login extends Component {
renderFields() {
return _.map(formFields, ({ label, name }) => {
return (
<Field
component={LoginField}
type="text"
key={label}
label={label}
name={name}
/>
);
});
}
render() {
const { handleSubmit, history } = this.props;
return (
<div>
<form
onSubmit={handleSubmit(values =>
loginFormSubmit(values, history, SubmissionError)
)}
>
{this.renderFields()}
<button type="submit">Send the Survey</button>
</form>
</div>
);
}
}
export default connect(
null,
{ loginFormSubmit }
)(
reduxForm({
form: "loginform"
})(Login)
);
You can see the handleSubmit is called when the form is submitted. handleSubmit calls our custom loginFormSubmit from redux. how can I check loginFormSubmit is called within handleSubmit. Here is my test so far
import { Login } from "components/Login/Login";
import { shallow } from "enzyme";
import React from "react";
describe("The Login component description", () => {
describe("The Login component", () => {
const props = {
handleSubmit: jest.fn()
};
it("should call handleSubmit on form submission", () => {
const wrapper = shallow(<Login {...props} />);
wrapper.find("button").simulate("click");
expect(props.handleSubmit).toHaveBeenCalled();
});
});
});

The module the function is imported from should be mocked at the top of the test:
import { loginFormSubmit } from "store/actions/profile/profile";
jest.mock('store/actions/profile/profile', () => ({ loginFormSubmit: jest.fn() }));
Then it can be asserted:
expect(props.handleSubmit).toHaveBeenCalledWith(expect.any(Function));
expect(loginFormSubmit).not.toHaveBeenCalled(...);
props.handleSubmit.mock.calls[0][0]()
expect(loginFormSubmit).toHaveBeenCalledWith(...);

Related

Pass text value to another component

How to pass text value to another component using Redux in React?
I am learning Redux in React. I am trying to pass text value to another component using Redux in React.
My code is like below
Mycomponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.dispatch({ type: "add" });
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
export default connect(mapStateToProps)(Mycomponent);
nameAction.js
export const nameAction = () => ({
type: 'add'
});
export default { nameAction };
nameReducer.js
const nameReducer = (state = {}, action) => {
switch (action.type) {
case 'add': {
return {
...state,
nameState: action.payload
};
}
default:
return state;
}
};
export default nameReducer;
Outputcomponent.js
import React, { Component } from 'react';
class Outputcomponent extends Component {
render = (props) => {
return (
<div>
<div>{this.props.nameState }</div>
</div>
);
}
}
export default Outputcomponent;
The use of redux hooks explained by Josiah is for me the best approach but you can also use mapDispatchToProps.
Even if the main problem is that you don't pass any data in your 'add' action.
nameAction.js
You call the action.payload in nameReducer.js but it does not appear in your action
export const nameAction = (text) => ({
type: 'add',
payload: text
});
Mycomponent.js
Then as for your state we can mapDispatchToProps.
(I think it's better to trigger the action with a submit button and save the input change in your textInput state, but I guess it's intentional that there is none)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {nameAction} from './nameAction'
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.nameAction(event.target.value);
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
const mapDispatchToProps = dispatch => ({ nameAction: (text) => dispatch(nameAction(text))});
export default connect(mapStateToProps,mapDispatchToProps)(Mycomponent);
OutputComponent.js
to get the data two possibilities either with a class using connect and mapStateToProps , or using the useSelector hook with a functional component.
with a Class
import React, { Component } from "react";
import { connect } from "react-redux";
class OutputComponent extends Component {
render = () => {
return (
<div>
<div>{this.props.nameState}</div>
</div>
);
};
}
const mapStateToProps = state => state;
export default connect(mapStateToProps)(OutputComponent);
with a functional component
import React from "react";
import { useSelector } from "react-redux";
const OutputComponent = () => {
const nameState = useSelector((state) => state.nameState);
return (
<div>
<div>{nameState}</div>
</div>
);
};
export default OutputComponent;
Of course you must not forget to create a strore and to provide it to the highest component
store.js
import { createStore } from "redux";
import nameReducer from "./nameReducer";
const store = createStore(nameReducer);
export default store;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Component
const AddTodo = () => {
const [todo, setTodo] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setTodo(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addTodoAction(todo));
}
return {
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
</form>
}
)
Actions
const addTodoAction = (text) => {
dispatch({
type: "ADD_TODO",
payload: text
})
}
Reducers
const addTodoReducer = (state, action) => {
switch(action.type) {
case "ADD_TODO":
return {
todo: action.payload,
}
default:
return state;
}
}
store
// some code for store.js
Accessing this todo from another component
const ComponentA = () => {
const {todo} = useSelector(state => state.todo);
return (
<p> {todo} </p>
)
}
Side Note:
Redux comes with too much boilerplate if you want to pass text from one component to another, just use props

I am facing problem to test my custom component

I have created my custom component NumberInput field. I am new in writing test cases so I just try to write one simple test case and want to execute it successfully.
Here is my component
import React from 'react';
import PropTypes from 'prop-types';
import NumberFormat from 'react-number-format';
import TextField from 'components/TextField';
function CustomInput(props) {
return <TextField {...props} />;
}
function NumberInput(props) {
const { onChange, ...otherProps } = props;
return (
<NumberFormat
thousandSeparator
decimalSeparator="."
decimalScale={2}
{...otherProps}
customInput={CustomInput}
onValueChange={values => {
const { value } = values;
onChange(value);
}}
/>
);
}
NumberInput.propTypes = {
onChange: PropTypes.func,
};
export default NumberInput;
and I am trying to write a test case for that
import React from 'react';
import { render, fireEvent } from '#testing-library/react';
import { NumberInput } from '../index';
describe('<NumberInputField />', () => {
it('Expect to have unit tests specified', () => {
const { container } = render(<NumberInput />);
const NumberFormat = container.firstChild
fireEvent.change(NumberFormat, { target: { value: 10 } });
expect(NumberFormat.value).toBe(10);
//expect(true).toEqual(false);
});
});
I am trying to write a test case using
Jest
testing-library/react
this is My Error
You are importing NumberInput as a named export, but it is actually a default export.
Change import { NumberInput } from '../index'; to import NumberInput from '../index';
OR
Change your export from export default NumberInput; to export { NumberInput };

TypeError: Cannot read property 'email' of undefined props using Jest and React. Why?

Trying to write unit tests for the ReactJs code using Jest. When I am trying to pass the props it shows me below error
TypeError: Cannot read property 'email' of undefined
62 |
63 | const mapStateToProps = state => {
> 64 | const { email, password, errors, loading } = state.auth;
| ^
65 |
66 | return { email, password, errors, loading };
67 | };
SignIn.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import * as actions from "Actions";
import classnames from "classnames";
class SignIn extends Component {
onSubmit(e) {
e.preventDefault();
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
render() {
const { email, password, errors, fieldChanged } = this.props;
return (
<div className="contentWrapper">
....
</div>
);
}
}
SignIn.propTypes = {
loginUser: PropTypes.func.isRequired,
fieldChanged: PropTypes.func.isRequired,
email: PropTypes.string.isRequired,
password: PropTypes.string.isRequired
};
const mapStateToProps = state => {
const { email, password, errors, loading } = state.auth;
return { email, password, errors, loading };
};
export default connect(
mapStateToProps,
actions
)(SignIn);
SignIn.test.js
import React, { Suspense } from 'react';
import Enzyme, {shallow} from 'enzyme';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-15';
Enzyme.configure({ adapter: new Adapter() });
import { Provider } from 'react-redux';
import configureMockStore from "redux-mock-store";
import thunk from 'redux-thunk';
import SignIn from '../containers/authentication/SignIn';
import mapStateToProps from "../containers/authentication/SignIn";
const mockStore = configureMockStore();
describe('SignIn', () => {
it('render sign in', () => {
const state = {
email: "aaky8668#gmail.com",
password: "pass123"
};
const store = mockStore(state);
const dwrapper = Enzyme.mount(<SignIn store={store} />);
console.log(dwrapper);
expect(dwrapper.props().email).toBe("aakshay8668#gmail.com")
});
});
Need to unit test SignIn and getting this error, how to map state with props?
What will be the correct way to map state with props.
The line of
const { email, password, errors, loading } = state.auth;
is attempting to destructure state.auth, it attempts to store its email, password, errors, loading members into variables with the same names. The error indicates that state.auth is undefined. You will need to make sure that state exists, it has an auth member which has attributes of the names that your code above expects.
I'm a proponent of bypassing the redux portion and testing against actions and reducers independently. Therefore, I'd recommend exporting the class and importing that for your test.
Working example (click the Tests tab to run tests):
containers/Login/index.js
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { login } from "../../actions/Auth";
import Form from "../../components/Form";
import SubTitle from "../../components/SubTitle";
import Title from "../../components/Title";
export class Login extends Component {
state = {
email: "",
password: ""
};
handleChange = ({ target: { name, value } }) =>
this.setState({ [name]: value });
handleSubmit = e => {
e.preventDefault();
const { email, password } = this.state;
if (!email || !password) return;
this.props.login(email);
};
render = () => (
<Fragment>
<Title>Login</Title>
<SubTitle>You must login before viewing the dashboard!</SubTitle>
<Form onSubmit={this.handleSubmit}>
<input
name="email"
type="email"
className="uk-input"
placeholder="email"
value={this.state.email}
onChange={this.handleChange}
/>
<input
className="uk-input"
name="password"
type="password"
placeholder="password"
value={this.state.password}
onChange={this.handleChange}
/>
<br />
<button className="uk-button uk-button-primary" type="submit">
Login
</button>
</Form>
</Fragment>
);
}
Login.propTypes = {
login: PropTypes.func.isRequired
};
export default connect(
null,
{ login }
)(Login);
containers/Login/__tests__/Login.test.js
import React from "react";
import { mount } from "enzyme";
import { Login } from "../index";
const login = jest.fn();
const initProps = {
login
};
describe("Login", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<Login {...initProps} />);
});
afterEach(() => {
login.mockClear();
});
it("renders without errors", () => {
expect(wrapper.find("form").exists()).toBeTruthy();
});
it("calls handleChange class field to update an input with a value", () => {
const value = "test#test.com";
wrapper
.find("input")
.first()
.simulate("change", { target: { value, name: "email" } });
expect(wrapper.state("email")).toEqual(value);
expect(
wrapper
.find("input")
.first()
.props().value
).toEqual(value);
});
it("doesn't call 'login' prop if email or password fields are empty", () => {
wrapper.find("form").simulate("submit");
expect(login).toHaveBeenCalledTimes(0);
});
it("calls 'login' prop to log in a user", () => {
const email = "test#test.com";
wrapper.setState({ email, password: "password" });
wrapper.find("form").simulate("submit");
expect(login).toHaveBeenCalledWith(email);
});
});
SignIn.test.js
The key too solution is that we have to add dive() to the shallow component so that it could get the component wrapper completely, this solution worked for me
import React, { Suspense } from 'react';
import Enzyme, { shallow, configur } from 'enzyme';
import EnzymeAdapter from 'enzyme-adapter-react-15';
import SignIn from '../containers/authentication/SignIn';
import configureStore from '../configureStore';
Enzyme.configure({
adapter: new EnzymeAdapter(),
disableLifecycleMethods: true
});
describe('SignIn', () => {
it('render sign in', () => {
const state = {
auth: {
email: "test#test.com",
password: "password",
errors: {
login: "red"
},
loading: true
}
};
const store = configureStore(state);
const dwrapper = shallow(<SignIn store={store}/>).dive();
expect(dwrapper.find(".contentWrapper").exists()).toBeTruthy();
});
});

How to test existence of an specific redux form with Jest and Enzyme?

I'm using Jest and Enzyme to write unit tests. At one of my React components I render two distinct redux forms.
A simplified version of it would be like:
// my_app.js
import React from 'react'
import MyForm from './my_form'
import MyOtherForm from './my_other_form'
class MyApp extends React.PureComponent {
render() {
return (
<div>
<MyForm />
<MyOtherForm />
</div>
)
}
}
// my_form.js
let MyForm = () => <form className='my-form' />
MyForm = reduxForm({ form: 'myForm' })(MyForm)
export default connect(state => state)(MyForm)
// my_other_form.js
let MyOtherForm = () => <form className='my-other-form' />
MyOtherForm = reduxForm({ form: 'myOtherForm' })(MyOtherForm)
export default connect(state => state)(MyOtherForm)
Write a simple test just to console log a shallow rendering would look like:
// test.js
import React from 'react'
import { shallow } from 'enzyme'
import MyApp from './my_app'
describe('<MyApp />'), () => {
let wrapper = shallow(<MyApp />)
it('should test something', () => {
console(wrapper.debug())
})
}
Would log something like:
<div>
<Connect(ReduxForm) />
<Connect(ReduxForm) />
</div>
How can I assert that both MyForm and MyOtherForm were rendered correctly?
Thanks!
You should use provider.
I use 'mount' instead of 'shallow' when using Provider.
Here is an example.
import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import LoginForm from '../LoginForm';
const mockStore = configureMockStore();
const dispatch = jest.fn();
const handleSubmit = jest.fn();
const onSubmit = jest.fn();
let store;
const initProps = {
onSubmit,
};
describe('LoginForm', () => {
beforeAll(() => {
store = mockStore({
handleSubmit,
dispatch,
});
dispatch.mockClear();
});
it('renders without exploding', () => {
const wrapper = mount(
<Provider store={store}>
<LoginForm {...initProps} />
</Provider>,
);
expect(wrapper).toHaveLength(1);
expect(wrapper.find('button').prop('disabled')).toBe(true);
});
});
Otherwise, you can wrap your form simply.
// LoginForm.jsx
const LoginForm = (props) => (
<form>
...
</form>
);
// LoginFormWrapper.jsx
import { reduxForm } from 'redux-form';
import LoginForm from './LoginForm';
export default reduxForm({
form: 'loginForm',
})(LoginForm);
// index.js
export { default } from './LoginFormWrapper';
//test.spec.js
import LoginForm from './LoginForm';
describe('LoginForm', () => {
const wrapper = shallow(<LoginForm />);
expect(wrapper).toHaveLength(1);
});

Redirect after successful async action with react router v3

I've tried a few ways of going about this where in my action is do the dispatch(push) :
import LoginService from '../../services/login-service';
export const ActionTypes = {
SET_LOGIN: 'SET_LOGIN',
}
export function getLogin(data, dispatch) {
LoginService.getLoginInfo(data).then((response) => {
const login = response.data;
dispatch({
type: ActionTypes.SET_LOGIN,
login
})
// .then((res) => {
// dispatch(push('/'));
// })
})
}
I even saw something about using the render property on the route for react router.
Now I am trying to use renderIf based on whether my state is true or false but I can't seem to get props on this page successfully :
import React from 'react';
import renderIf from 'render-if';
import { Redirect } from 'react-router-dom';
import LoginHeader from '../../components/header/login-header';
import LoginPage from './login-page';
const LoginHome = props => (
<div>
{console.log(props)}
{renderIf(props.loginReducer.status === false)(
<div>
<LoginHeader />
<LoginPage />}
</div>)}
{renderIf(props.loginReducer.status === true)(
<Redirect to="/" />,
)}
</div>
);
export default LoginHome;
Here is loginPage:
import React from 'react';
import { form, FieldGroup, FormGroup, Checkbox, Button } from 'react-bootstrap';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { connect } from 'react-redux';
import { getLogin } from './login-actions';
import LoginForm from './form';
import logoSrc from '../../assets/images/logo/K.png';
import './login.scss'
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
passwordVisible: false,
}
this.handleSubmit= this.handleSubmit.bind(this);
this.togglePasswordVisibility= this.togglePasswordVisibility.bind(this);
}
togglePasswordVisibility() {
this.setState((prevState) => {
if (prevState.passwordVisible === false) {
return { passwordVisible: prevState.passwordVisible = true }
}
else if (prevState.passwordVisible === true) {
return { passwordVisible: prevState.passwordVisible = false }
}
})
}
handleSubmit(values) {
this.props.onSubmit(values)
}
render () {
return (
<div id="background">
<div className="form-wrapper">
<img
className="formLogo"
src={logoSrc}
alt="K"/>
<LoginForm onSubmit={this.handleSubmit} passwordVisible={this.state.passwordVisible}
togglePasswordVisibility={this.togglePasswordVisibility}/>
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
loginReducer: state.loginReducer,
};
}
function mapDispatchToProps(dispatch) {
return {
onSubmit: (data) => {
getLogin(data, dispatch);
},
};
}
export default connect (mapStateToProps, mapDispatchToProps)(LoginPage);
Let me know if anything else is needed
Use the withRouter HoC from React Router to provide { match, history, location } to your component as follows;
import { withRouter } from "react-router";
...
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginPage));
Modify getLogin to accept history as a third argument; export function getLogin(data, dispatch, history) { ... }
Now in the then method of your async action you can use history.push() or history.replace() to change your current location. <Redirect /> uses history.replace() internally.
You can read about the history module here. This is what is used internally by React Router.
EDIT: In response to your comment...
With your current setup you will need to pass history into getLogin through your mapDispatchToProps provided onSubmit prop.
function mapDispatchToProps(dispatch) {
return {
onSubmit: (data, history) => {
getLogin(data, dispatch, history);
},
};
}
Your handleSubmit method will also need updated.
handleSubmit(values) {
this.props.onSubmit(values, this.props.history)
}
Modified answer depending on how Router was set up I had to use hashHistory: react-router " Cannot read property 'push' of undefined"

Resources