Cannot simulate form clicks in Redux-Form unit test - reactjs

I'm trying to simulate a form submission in redux-form for a unit-test and I can't even get the onSubmit handler to be called.
Test Snippet
import React from 'react';
import { AddBudgetForm } from '../../../src/Budget/components/AddBudgetForm';
import { SubmissionError } from 'redux-form';
import { shallow, configure } from 'enzyme';
import sinon from 'sinon';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
describe("AddBudgetForm Component", () => {
let budgetForm = null;
let submitting, touched, error, reset,addBudget, addBudgetResponse, handleSubmit
beforeEach(() => {
submitting = false
touched = false
error = null
reset = sinon.spy()
addBudgetResponse = Promise.resolve()
handleSubmit = fn => fn
})
const buildForm = () => {
addBudget = sinon.stub().returns(addBudgetResponse)
const props = {
addBudget,
submitting: submitting,
fields: {
budgetName: {
value: '',
touched: touched,
error: error
}
},
handleSubmit,
reset
}
return shallow(<AddBudgetForm {...props}/>)
}
it ('Calls reset after onSave', () => {
budgetForm = buildForm();
budgetForm.find('[type="submit"]').simulate('submit')
})
})
Above, I'm mocking some of the actions and my test will inevitably just check the callCount from the sinon spy.
Form
submit(dataValues) {
console.log("called")
dataValues.preventDefault();
this.props.addBudget({})
}
render() {
const { handleSubmit } = this.props;
return (
<div className='form-group has-danger'>
<form onSubmit={(e) => this.submit(e)}>
<Field name='budgetName' component={this.categoryInput} placeholder="Enter Category of Budget" label="Budget Category"/>
<button type='submit' className='btn btn-primary'>Submit</button>
</form>
</div>
)
}
}
I've shortened my code a bit, but the idea is that the onSubmit should call the function, but I can't even get the console log to print. Not really sure why this is happening.

If you want to simulate a submit event you should select a form tag. With the code above should be a click. So:
budgetForm.find('[type="submit"]').simulate('click')

Related

Mocking axios.post response triggered by button in react component

I would like to test (with RTL and jest) the api.post (with axios) triggered by a button in my react component bellow. It's about a login display with 2 inputs fields (email and password) and 2 buttons (enter and register). Both the enter and the register buttons called asynchronous functions.
// src > pages > Login
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { saveToken } from '../app/slices/tokenSlice';
import { saveUserData } from '../app/slices/userSlice';
import api from '../services/api';
function Login() {
const [loginState, setLoginState] = useState({
email: '',
password: '',
});
const navigate = useNavigate();
const dispatch = useDispatch();
const handleChange = ({ target: { name, value } }) => {
setLoginState({
...loginState,
[name]: value,
});
};
const enterUser = async () => {
await api.post('/login', { ...loginState })
.then((response) => {
dispatch(saveToken(response.data.token));
dispatch(saveUserData(loginState));
navigate('/home');
})
.catch((error) => {
alert(error.message);
});
};
const registerUser = async () => {
api.post('/user', loginState)
.then(() => {
alert('Usuário cadastrado com sucesso!');
})
.catch((error) => {
alert(error.message);
});
};
return (
<div>
<label htmlFor="email">
Email:
<input
id="email"
name="email"
onChange={handleChange}
placeholder="Email"
type="text"
value={loginState.email}
/>
</label>
<label htmlFor="password">
Senha:
<input
id="password"
name="password"
onChange={handleChange}
type="password"
value={loginState.password}
/>
</label>
<button
name="btnEnterUser"
type="button"
onClick={() => enterUser()}
>
Entrar
</button>
<button
name="btnRegisterUser"
type="button"
onClick={() => registerUser()}
>
Cadastrar
</button>
</div>
);
}
export default Login;
// src > services > api.js
import axios from 'axios';
const { BASE_URL } = process.env;
const api = axios.create({
baseURL: BASE_URL,
});
api.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8';
api.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
export default api;
How can i develop a test with mock data related to "registerUser" and "enterUser" functions. I have tried many options with jest.fn(), jest.spyOn() and userEvent.click(btnRegister), where btnRegister is the element which i getByRole as you can check in my test file bellow:
// src > tests > login.test.js
import React from 'react';
import '#testing-library/jest-dom';
import { screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import Login from '../pages/Login';
import renderWithRouter from './renderWithRouter';
import api from '../services/api';
describe('Componente Login', () => {
let inputEmail;
let inputPassword;
let btnRegister;
let btnEnter;
const userEmail = 'mariana#gmail.com';
const userPassword = '123456';
const tokenMock = {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjA4MWYyYzBhMWFhNzFmYjhjZjU2NjAiLCJlbWFpbCI6Im1hcmlhbmFAZ21haWwuY29tIiwiaWF0IjoxNjQ1MzIyMjU1LCJleHAiOjE2NDUzNDAyNTV9.TIgJFIzg1W0bisvJ3CfRsVCZr3kbKn13_NBN-Ah1U1w',
};
const userRegisteredResponseMock = {
user:
{
email: 'vitao#gmail.com',
id: '6211a1d3eb25fc2418dec05a',
},
};
beforeAll(() => {
renderWithRouter(<Login />);
inputEmail = screen.getByLabelText(/email/i);
inputPassword = screen.getByLabelText(/senha/i);
btnRegister = screen.getByRole('button', { name: /cadastrar/i });
btnEnter = screen.getByRole('button', { name: /entrar/i });
userEvent.type(inputEmail, userEmail);
userEvent.type(inputPassword, userPassword);
});
it('Registro de usuário com sucesso', async () => {
// ************** DOESNT WORK **********************
// api.post = jest.fn().mockImplementation(() => {
// Promise.resolve(userRegisteredResponseMock);
// });
// api.post = jest.fn(() => Promise.resolve(userRegisteredResponseMock));
// api.post = jest.fn().mockResolvedValue(userRegisteredResponseMock);
// jest.spyOn(Login, 'enterUser');
// ****************************************************
userEvent.click(btnRegister);
expect(<"REGISTER USER" FUNCTION>).toBeCalledTimes(1);
});
});
I also have tried created "mocks" folder as jest documentation has mentioned in this link: https://jestjs.io/docs/manual-mocks , but without success.
in order to force jest using the mocked module, the jest.mock() function should be called.
import api from '../services/api';
...
jest.mock('../services/api');
...
api.post.mockResolvedValue(userRegisteredResponseMock);
it's also possible to mock axios module itself. furthermore, there's a jest-mock-axios npm module designed to achieve the behaviour.

How to test a controlled input (via Redux state) that dispatches an action on its onChange handler?

This is my component:
import React, { ChangeEvent } from "react";
import { Input } from "#chakra-ui/react"
import { useDispatch, useSelector } from "react-redux";
import { ACTION } from "../../redux/appSlice";
import { RootState } from "../../redux/store";
interface IAddTodoForm {}
const AddTodoForm: React.FC<IAddTodoForm> = (props) => {
const dispatch = useDispatch();
const { addTodoForm } = useSelector((state: RootState) => state.app);
const { title } = addTodoForm;
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
const title = e.target.value;
dispatch(ACTION.UPDATE_TODO_INPUT({title}));
};
return(
<Input
value={title}
onChange={onChange}
/>
);
};
export default React.memo(AddTodoForm);
It's a basic input that dispatches to Redux from the onChange handler.
This is what I'd like to test:
import { render, screen, fireEvent } from "../../../test/test-utils";
import AddTodoForm from "../AddTodoForm";
beforeEach(() => {
render(<AddTodoForm/>); // NOTE: THIS IS A CUSTOM render FUNCTION THAT ALREADY WRAPPED WITH THE <Provider store={store}> FROM react-redux
});
test("AddTodoForm updated input vale", () => {
const { container } = render(<AddTodoForm/>);
const input = container.getElementsByTagName("input")[0];
expect(input).toBeInTheDocument();
fireEvent.change(input, { target: { value: "NEW TODO" }});
// HERE I WOULD LIKE TO CHECK IF THE INPUT VALUE HAS BEEN UPDATE
// HOW CAN I DO THAT
});
As you can see, I would like to fire a change event, that should dispatch to the Redux store, and then I would like to confirm that the input has been updated with the NEW TODO value. Is this the correct approach?
You would simply use an expect like so:
test("AddTodoForm updated input vale", () => {
const input = screen.getByLabelText("add-todo-input");
expect(input).toBeInTheDocument();
fireEvent.change(input, { target: { value: "NEW TODO" }});
expect(input.value).toBe('NEW TODO')
});
For an async operation, you could use this method instead:
await screen.findByText("NEW TODO");
expect(getByText("NEW TODO")).toBeTruthy();
By using await findByText you wait for the text to appear.

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();
});
});

Enzyme/Jest onSubmit not calling Submit Function

I'm having trouble checking if my mock action was called from an onSubmit on a form:
Login Form:
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
};
}
handleSubmit = e => {
e.preventDefault();
this.props.loginUser(this.state.email, this.state.password);
};
handleChange = event => {
const { value } = event.target;
const { name } = event.target;
this.setState({
[name]: value,
});
};
render() {
const { t } = this.props;
return (
<div className="login-container>
<form data-test-id="login-form" onSubmit={this.handleSubmit}>
<TextComponent
label="Email"
testId="login-email"
value={this.state.email}
handleChange={this.handleChange}
/>
<div className="my-3" />
<TextComponent
label="Password"
testId="login-password"
value={this.state.password}
handleChange={this.handleChange}
/>
<button action="submit" data-test-id="login-button">
{t("en.login")}
</button>
</form>
</div>
);
}
}
function mapStateToProps(state) {
return {
authenticated: state.login.authenticated,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ loginUser }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(withTranslation()(Login));
Test:
import { mount, shallow, render } from "enzyme";
import React from "react";
import { Provider } from "react-redux";
import { I18nextProvider } from "react-i18next";
import { Router } from "react-router-dom";
import { findByTestAtrr, testStore } from "../../test/utils/utils";
import Login from "../../src/Login/index";
import i18n from "../../src/i18n";
import history from "../../src/history";
const setUp = loginUser => {
const initialState = {
login: {
authenticated: true,
},
};
const store = testStore(initialState);
const wrapper = mount(
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<Router history={history}>
<Login onSubmit={loginUser} />
</Router>
</Provider>
</I18nextProvider>
);
return wrapper;
};
describe("submit button", () => {
let emailInput;
let passwordInput;
let submitButton;
let newWrapper;
let loginAction;
beforeEach(() => {
loginAction = jest.fn();
newWrapper = setUp(loginAction);
emailInput = findByTestAtrr(newWrapper, "login-email");
emailInput.instance().value = "email.com";
emailInput.simulate("change");
passwordInput = findByTestAtrr(newWrapper, "login-password");
passwordInput.instance().value = "password";
passwordInput.simulate("change");
submitButton = findByTestAtrr(newWrapper, "login-button");
});
it("login action is called", () => {
console.log(submitButton.debug());
submitButton.simulate("submit");
expect(loginAction).toHaveBeenCalledTimes(1);
});
});
});
I'm able to simulate adding values to the email and password but I can't simulate the onClick to work. Am I testing the submit function incorrectly?
This is my submit button when I console.log
console.log __tests__/integration_tests/login.test.js:97
<button action="submit" data-test-id="login-button">
Log in
</button>
error:
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
As raised from 2016 on github: Simulated click on submit button in form does not submit form #308, this issue still persists on my project, using Jest + Enzyme (2020).
There are some workarounds such as using tape or sino.
The other solution is to get the DOM element and trigger the click without using simulate (go to given link, scroll down the thread to learn more).
If anyone ever found a solution to simulate form submit, kindly tag me here. Thank you.
As explained in the below mentioned link, you can use done and setTimeout.
https://github.com/ant-design/ant-design/issues/21272#issuecomment-628607141
it('login action is called', (done) => {
submitButton.simulate("submit");
setTimeout(() => {
expect(loginAction).toHaveBeenCalledTimes(1);
done();
}, 0);
});
it('should call onSubmit method', ()=>{
const spy = jest.spyOn(wrapper.instance(), "onSubmit");
wrapper.find('button[type="submit"]').simulate('click');
expect(spy).toHaveBeenCalledTimes(1);
});
Check the name of method to be called on simulate click. In my case it was onSubmit.
No need to import sinon or spy from sinon jest does everything.

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

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(...);

Resources