enzyme testing - can't find button - reactjs

I have a fairly basic react component in a React app. I want to test that the "submitted" portion of the state changes from false to true when the form is submitted. Not particularly hard. But the enzyme test seems unable to find the button. Not sure if it has to do with the if/else statement.
Here is the component:
import React from 'react';
import { connect } from 'react-redux';
import { questionSubmit } from '../actions/users';
import { getCurrentUser, clearMessage } from '../actions/auth';
export class AnswerForm extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
}
}
handleFormSubmit(event) {
event.preventDefault();
this.setState({ submitted: true });
this.props.dispatch(questionSubmit(this.answerInput.value, this.props.currentUsername));
this.answerInput.value = '';
}
handleNextButton() {
this.setState({ submitted: false });
this.props.dispatch(getCurrentUser(this.props.currentUsername))
}
render() {
let nextButton;
let form;
let message = <p>{this.props.message}</p>
if (this.state.submitted) {
nextButton = <button className="button-next" onClick={() => this.handleNextButton()}>Next</button>;
}
else {
form =
<form onSubmit={e => this.handleFormSubmit(e)}>
<input className="input-answer" ref={input => this.answerInput = input}
placeholder="Your answer" />
<button id="button-answer" type="submit">Submit</button>
</form>;
}
return (
<div>
<p>{this.props.message}</p>
{form}
{nextButton}
</div>
)
}
}
export const mapStateToProps = (state, props) => {
return {
message: state.auth.message ? state.auth.message : null,
currentUsername: state.auth.currentUser ? state.auth.currentUser.username : null,
question: state.auth.currentUser ? state.auth.currentUser.question : null
}
}
export default connect(mapStateToProps)(AnswerForm);
Here is the test:
import React from 'react';
import {AnswerForm} from '../components/answer-form';
import {shallow, mount} from 'enzyme';
describe('<AnswerForm />', () => {
it('changes submitted state', () => {
const spy = jest.fn();
const wrapper = mount(<AnswerForm dispatch={spy}/> );
wrapper.instance();
expect(wrapper.state('submitted')).toEqual(false);
const button = wrapper.find('#button-answer');
button.simulate('click')
expect(wrapper.state('submitted')).toEqual(true);
});
});
I get this error when I try running this test:
expect(received).toEqual(expected)
Expected value to equal:
true
Received:
false
at Object.it (src/tests/answer-form.test.js:24:44)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
Any ideas? It's a pretty straight shot other than the if statement. Not sure what is going on here.

The issue here is that the intrinsic DOM event propagation that is expected to occur between a submit button and a form element is not being done by enzyme or React during simulation.
The event system in React is all synthetic in order to normalise browser quirks, they actually all get added to document (not the node you add the handler to) and fake events are bubbled through the components by React (I highly recommend watching this webinar from the React core team explaining in event system in depth)
This makes testing them a little unintuitive and sometimes problematic, because simulation does not trigger real DOM event propagation
In enzyme, events triggered on shallow renders are not real events at all and will not have associated DOM target. Even when using mount which does have a DOM fragment backing it, it still uses React's synthetic event system, so simulate still only tests synthetic events bubbling though your components, they do not propagate via real DOM, so simulating a click on a submit button does not in turn intrinsically trigger a submit DOM event on the form itself, as its the browser not React that is responsible for that. https://github.com/airbnb/enzyme/issues/308
So two ways to get around that in a test are...
1) Not ideal from a UI test perspective as bypasses button, but clean for a unit test, especially as it should work with shallow rendering to isolate the component.
describe('<AnswerForm />', () => {
const spy = jest.fn();
const wrapper = shallow(<AnswerForm dispatch={spy}/> );
it('should show form initially', () => {
expect(wrapper.find('form').length).toEqual(0);
})
describe('when the form is submitted', () => {
before(() => wrapper.find('form').simulate('submit')))
it('should have dispatched the answer', () => {
expect(spy).toHaveBeenCalled();
});
it('should not show the form', () => {
expect(wrapper.find('form').length).toEqual(0);
});
it('should show the "next" button', () => {
expect(wrapper.find('#button-next').length).toEqual(1);
});
});
});
2) Trigger a real click event on DOM button element itself, rather than simulating it on your component as if it were a Selenium functional test (so feels a little dirty here), which the browser will propagate into a form submit before React catches the submit event and takes over with synthetic events. Therefore this only works with mount
describe('<AnswerForm />', () => {
const spy = jest.fn();
const wrapper = mount(<AnswerForm dispatch={spy}/> );
it('should show form initially', () => {
expect(wrapper.find('form').length).toEqual(0);
})
describe('when form is submitted by clicking submit button', () => {
before(() => wrapper.find('#button-answer').getDOMNode().click())
it('should have dispatched the answer', () => {
expect(spy).toHaveBeenCalled();
});
it('should not show the form', () => {
expect(wrapper.find('form').length).toEqual(0);
});
it('should show the "next" button', () => {
expect(wrapper.find('#button-next').length).toEqual(1);
});
});
});
You'll also note I'm not testing state itself. It's generally bad practice to test state directly as its pure implementation detail (state change should eventually cause something more tangible to happen to the component that can instead be tested).
Here I have instead tested that your event causes the dispatch spy to have been called with correct args, and that the Next button is now shown instead of the form. That way it is more focused on outcomes and less brittle should you ever refactor the internals.

Be mindful that the component you are testing is not the AnswerForm class component, but rather the wrapped component created by passing AnswerForm to react-redux's connect higher order component.
If you use shallow rendering rather than full mounting, you can use the dive() function of the Enzyme API to get to your actual class component. Try this:
import React from 'react';
import {AnswerForm} from '../components/answer-form';
import {shallow, mount} from 'enzyme';
describe('<AnswerForm />', () => {
it('changes submitted state', () => {
const spy = jest.fn();
const wrapper = shallow(<AnswerForm dispatch={spy}/> );
expect(wrapper.dive().state('submitted')).toEqual(false);
const button = wrapper.dive().find('#button-answer');
button.simulate('click')
expect(wrapper.dive().state('submitted')).toEqual(true);
});
});
Another option is to test the non-wrapped component instance directly. To do this, you just need to change your export and import. In answer-form.js:
export class AnswerForm extends React.Component
...your code
export default connect(mapStateToProps)(AnswerForm);
This exports the non-wrapped component in addition to the wrapped component. Then your imports in answer-form.test.js:
import WrappedAnswerForm, { AnswerForm } from 'path/to/answer-form.js`;
This way, you can test AnswerForm functionality independently, assuming you don't need to test any Redux received props. Check out this GitHub issue for more guidance.

Related

How to test onClick with Jest that is NOT a callback function in props?

I found lots of ways of using mock functions in jest to spy on callback functions that are passed down to a component but nothing on testing a simple onClick that is defined in the same component.
My Example Page:
const ExamplePage: NextPage = () => {
const router = useRouter();
const onClick = (): Promise<void> => {
axios.post(`/api/track`, {
eventName: Event.TRACK_CLICK,
});
router.push("/new-route");
return Promise.resolve();
};
return (
<Container data-testid="container">
<Title>Example Title</Title>
<CreateButton data-testid="create-button" onClick={onClick}>
Create Partner
</CreateButton>
</Container>
);
};
export default ExamplePage;
My current test where I am attempting to get the onClick from getAttribute:
import { fireEvent, render } from "../../../../test/customRenderer";
import ExamplePage from "../../../pages/example-page";
describe("Example page", () => {
it("has a button to create", () => {
const { getByTestId } = render(<ExamplePage />);
const createButton = getByTestId("create-button");
expect(createButton).toBeInTheDocument();
});
it(" the button's OnClick function should be executed when clicked", () => {
const { getByTestId } = render(<ExamplePage />);
// find the button
const createButton = getByTestId("create-button");
// check the button has onClick
expect(createButton).toHaveAttribute("onClick");
// get the onClick function
const onClick = createButton.getAttribute("onClick");
fireEvent.click(createButton);
// check if the button's onClick function has been executed
expect(onClick).toHaveBeenCalled();
});
});
The above fails since there is no onClick attribute only null. My comments in the test highlight my thought process of trying to reach down into this component for the function on the button and checking if it has been called.
Is there any way to test a onClick that is self contained in a react component?
You need to provide mocked router provider and expect that a certain route is pushed to the routers. You also need extract the RestAPI into a separate module and mock it! You can use Dependency Injection, IOC container or import the Api in the component and mock it using jest. I will leave the RestAPi mocking to you.
Mocking router details here: How to mock useRouter
const useRouter = jest.spyOn(require('next/router'), 'useRouter')
describe("", () => {
it("",() => {
const pushMock = jest.fn();
// Mocking Rest api call depends on how you are going to "inject it" in the component
const restApiMock = jest.jn().mockResolvedValue();
useRouter.mockImplementationOnce(() => ({
push: pushMock,
}))
const rendrResult = render(<ExamplePage />);
//get and click the create button
//expect the "side" effects of clicking the button
expect(restApiMock).toHaveBeenCalled();
expect(pushMock).toHaveBeenCalledWith("/new-route");
});
});

React Simulate back button behavior in react testing library and jest

Hey guys I've got a component that overrides the back button behavior by creating a popstate event, but I haven't found a way to test it's functionality. It should be as easy as creating a spy and checking if the window.confirm is being called, but it's not calling the function when I do window.history.back(), and I don't understand why.
Also if I pull the function outside of the component, it's being rendered as an anonymous function and the remove event listener isn't being called, which makes the popup event display on every page, and there's no way to remove it because it's an anonymous function. I'm able to test the function though and the logic is working just fine (;
How do we fix this? What should I do?
This function stops the initial back button navigation behavior, and creates a popup event to ask if the person wants to navigate to the home page, and if they click okay then they navigate, otherwise they stay on the page. We have this wrapped around a page after they submit a form to prevent them from submitting another form when they click the back button
Here's my component:
import React, {useEffect, ReactElement} from 'react';
import { navigate } from '#reach/router';
export interface BackButtonBehaviorProps {
children: ReactElement;
}
export const BackButtonBehavior = ({children}: BackButtonBehaviorProps) => {
const onBackButtonEvent = (e: { preventDefault: () => void; }) => {
e.preventDefault();
const backButtonIsConfirmed = window.confirm("Your claim has been submitted, would you like to exit before getting additional claim information?");
if (backButtonIsConfirmed) {
navigate('/');
} else {
window.history.pushState(window.history.state, "success page", window.location.pathname); // When you click back (this refreshes the current instance)
}
};
useEffect(() => {
window.history.pushState(window.history.state, "", window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent, true);
// As you're unmounting the component
return () => {
window.removeEventListener('popstate', onBackButtonEvent);
};
}, []);
return (children);
};
If I pull the function outside of the component and export it, it's rendered in the popstate event listeners as anonymous, and will not be deleted when I'm unmounting the component, and there's no way to fix that. Here are the tests that work when I exported it though:
import {cleanup, render} from '#testing-library/react';
import * as router from '#reach/router';
import { mockPersonalReturnObj } from '../Summary/testData';
describe('<App />', () => {
let navigateSpy: any;
let backButtonBehavior: any;
beforeEach(() => {
const mod = require('./BackButtonBehavior');
backButtonBehavior = mod.onBackButtonEvent;
navigateSpy = jest.spyOn(router, 'navigate');
});
afterEach(() => {
jest.resetAllMocks();
cleanup();
});
it('should display a popup when the user clicks the back button', async () => {
jest.spyOn(global, 'confirm' as any).mockReturnValueOnce(true);
backButtonBehavior({preventDefault: () => {}});
expect(global.confirm).toHaveBeenCalled();
});
it('should navigate to home page when you click ok on the window.confirm popup', async () => {
jest.spyOn(global, 'confirm' as any).mockReturnValueOnce(true);
backButtonBehavior({preventDefault: () => {}});
expect(global.confirm).toHaveBeenCalled();
expect(await navigateSpy).toHaveBeenCalledWith('/');
});
});
I haven't found a way to call the global confirm when I do a test and I literally just want to test if the window.confirm event is being called (there's a bunch of ways to check window.confirm through spies, none of them worked). I need a way to simulate the back button click event for this to be called, but I haven't found a way to do this. Here's a test example:
it('should navigate to home page when you click ok on the window.confirm popup', async () => {
jest.spyOn(global, 'confirm' as any).mockReturnValueOnce(true);
render(
<BackButtonBehavior>
<CurrentPage />
</BackButtonBehavior>
);
window.history.back();
expect(global.confirm).toHaveBeenCalled();
expect(await navigateSpy).toHaveBeenCalledWith('/', {});
});
How do we simulate clicking the back button in the browser for react tests?

How to test react-toastify with jest and react-testing-library

I have a screen with some form, and on submission, I send the request to back-end with axios. After successfully receiving the response, I show a toast with react-toastify. Pretty straight forward screen. However, when I try to test this behavior with an integration test using jest and react testing library, I can't seem to make the toast appear on DOM.
I have a utility renderer like that to render the component that I'm testing with toast container:
import {render} from "#testing-library/react";
import React from "react";
import {ToastContainer} from "react-toastify";
export const renderWithToastify = (component) => (
render(
<div>
{component}
<ToastContainer/>
</div>
)
);
In the test itself, I fill the form with react-testing-library, pressing the submit button, and waiting for the toast to show up. I'm using mock service worker to mock the response. I confirmed that the response is returned OK, but for some reason, the toast refuses to show up. My current test is as follows:
expect(await screen.findByRole("alert")).toBeInTheDocument();
I'm looking for an element with role alert. But this seems to be not working.
Also, I tried doing something like this:
...
beforeAll(() => {
jest.useFakeTimers();
}
...
it("test", () => {
...
act(() =>
jest.runAllTimers();
)
expect(await screen.findByRole("alert")).toBeInTheDocument();
}
I'm kind of new to JS, and the problem is probably due to asynch nature of both axios and react-toastify, but I don't know how to test this behavior. I tried a lot of things, including mocking timers and running them, mocking timers and advancing them, not mocking them and waiting etc. I even tried to mock the call to toast, but I couldn't get it working properly. Plus this seems like an implementation detail, so I don't think I should be mocking that.
I think the problem is I show the toast after the axios promise is resolved, so timers gets confused somehow.
I tried to search many places, but failed to find an answer.
Thanks in advance.
Thank you #Estus Flask, but the problem was much much more stupid :) I had to render ToastContainer before my component, like this:
import {render} from "#testing-library/react";
import React from "react";
import {ToastContainer} from "react-toastify";
export const renderWithToastify = (component) => {
return (
render(
<div>
<ToastContainer/>
{component}
</div>
)
);
};
Then, the test was very simple, I just had to await on the title of the toast:
expect(await screen.findByText("alert text")).toBeInTheDocument();
The findByRole doesn't seem to work for some reason, but I'm too tired to dig deeper :)
I didn't have to use any fake timers or flush the promises. Apperently, RTL already does those when you use await and finBy* queries, only the order of rendering was wrong.
In order to use a mock when you don't have access to the DOM (like a Redux side effect) you can do:
import { toast } from 'react-toastify'
jest.mock('react-toastify', () => ({
toast: {
success: jest.fn(),
},
}))
expect(toast.success).toHaveBeenCalled()
What I would do is mock the method from react-toastify to spy on that method to see what is gets called it, but not the actual component appearing on screen:
// setupTests.js
jest.mock('react-toastify', () => {
const actual = jest.requireActual('react-toastify');
Object.assign(actual, {toast: jest.fn()});
return actual;
});
and then in the actual test:
// test.spec.js
import {toast} from 'react-toastify';
const toastCalls = []
const spy = toast.mockImplementation((...args) => {
toastCalls.push(args)
}
)
describe('...', () => {
it('should ...', () => {
// do something that calls the toast
...
// then
expect(toastCalls).toEqual(...)
}
}
)
Another recommendation would be to put this mockImplementation into a separate helper function which you can easily call for the tests you need it for. This is a bear bones approach:
function startMonitoring() {
const monitor = {toast: [], log: [], api: [], navigation: []};
toast.mockImplementation((...args) => {
monitor.toast.push(args);
});
log.mockImplementation((...args) => {
monitor.log.push(args);
});
api.mockImplementation((...args) => {
monitor.api.push(args);
});
navigation.mockImplementation((...args) => {
monitor.navigation.push(args);
});
return () => monitor;
}
it('should...', () => {
const getSpyCalls = startMonitoring();
// do something
expect(getSpyCalls()).toEqual({
toast: [...],
log: [...],
api: [...],
navigation: [...]
});
});
Here, the solution was use getByText:
await waitFor(() => {
expect(screen.getByText(/Logged!/i)).toBeTruthy()
})

Testing React Functional Component with Hooks using Jest

So I'm moving away from class based components to functional components but am stuck while writing test with jest/enzyme for the methods inside the functional components which explicitly uses hooks. Here is the stripped down version of my code.
function validateEmail(email: string): boolean {
return email.includes('#');
}
const Login: React.FC<IProps> = (props) => {
const [isLoginDisabled, setIsLoginDisabled] = React.useState<boolean>(true);
const [email, setEmail] = React.useState<string>('');
const [password, setPassword] = React.useState<string>('');
React.useLayoutEffect(() => {
validateForm();
}, [email, password]);
const validateForm = () => {
setIsLoginDisabled(password.length < 8 || !validateEmail(email));
};
const handleEmailChange = (evt: React.FormEvent<HTMLFormElement>) => {
const emailValue = (evt.target as HTMLInputElement).value.trim();
setEmail(emailValue);
};
const handlePasswordChange = (evt: React.FormEvent<HTMLFormElement>) => {
const passwordValue = (evt.target as HTMLInputElement).value.trim();
setPassword(passwordValue);
};
const handleSubmit = () => {
setIsLoginDisabled(true);
// ajax().then(() => { setIsLoginDisabled(false); });
};
const renderSigninForm = () => (
<>
<form>
<Email
isValid={validateEmail(email)}
onBlur={handleEmailChange}
/>
<Password
onChange={handlePasswordChange}
/>
<Button onClick={handleSubmit} disabled={isLoginDisabled}>Login</Button>
</form>
</>
);
return (
<>
{renderSigninForm()}
</>);
};
export default Login;
I know I can write tests for validateEmail by exporting it. But what about testing the validateForm or handleSubmit methods. If it were a class based components I could just shallow the component and use it from the instance as
const wrapper = shallow(<Login />);
wrapper.instance().validateForm()
But this doesn't work with functional components as the internal methods can't be accessed this way. Is there any way to access these methods or should the functional components be treated as a blackbox while testing?
In my opinion, you shouldn't worry about individually testing out methods inside the FC, rather testing it's side effects.
eg:
it('should disable submit button on submit click', () => {
const wrapper = mount(<Login />);
const submitButton = wrapper.find(Button);
submitButton.simulate('click');
expect(submitButton.prop('disabled')).toBeTruthy();
});
Since you might be using useEffect which is async, you might want to wrap your expect in a setTimeout:
setTimeout(() => {
expect(submitButton.prop('disabled')).toBeTruthy();
});
Another thing you might want to do, is extract any logic that has nothing to do with interacting with the form intro pure functions.
eg:
instead of:
setIsLoginDisabled(password.length < 8 || !validateEmail(email));
You can refactor:
Helpers.js
export const isPasswordValid = (password) => password.length > 8;
export const isEmailValid = (email) => {
const regEx = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regEx.test(email.trim().toLowerCase())
}
LoginComponent.jsx
import { isPasswordValid, isEmailValid } from './Helpers';
....
const validateForm = () => {
setIsLoginDisabled(!isPasswordValid(password) || !isEmailValid(email));
};
....
This way you could individually test isPasswordValid and isEmailValid, and then when testing the Login component, you can mock your imports. And then the only things left to test for your Login component would be that on click, the imported methods get called, and then the behaviour based on those response
eg:
- it('should invoke isPasswordValid on submit')
- it('should invoke isEmailValid on submit')
- it('should disable submit button if email is invalid') (isEmailValid mocked to false)
- it('should disable submit button if password is invalid') (isPasswordValid mocked to false)
- it('should enable submit button if email is invalid') (isEmailValid and isPasswordValid mocked to true)
The main advantage with this approach is that the Login component should just handle updating the form and nothing else. And that can be tested pretty straight forward. Any other logic, should be handled separately (separation of concerns).
Cannot write comments but you must note that what Alex Stoicuta said is wrong:
setTimeout(() => {
expect(submitButton.prop('disabled')).toBeTruthy();
});
this assertion will always pass, because ... it's never executed. Count how many assertions are in your test and write the following, because only one assertion is performed instead of two. So check your tests now for false positive)
it('should fail',()=>{
expect.assertions(2);
expect(true).toEqual(true);
setTimeout(()=>{
expect(true).toEqual(true)
})
})
Answering your question, how do you test hooks? I don't know, looking for an answer myself, because for some reason the useLayoutEffect is not being tested for me...
So by taking Alex's answer I was able to formulate the following method to test the component.
describe('<Login /> with no props', () => {
const container = shallow(<Login />);
it('should match the snapshot', () => {
expect(container.html()).toMatchSnapshot();
});
it('should have an email field', () => {
expect(container.find('Email').length).toEqual(1);
});
it('should have proper props for email field', () => {
expect(container.find('Email').props()).toEqual({
onBlur: expect.any(Function),
isValid: false,
});
});
it('should have a password field', () => {
expect(container.find('Password').length).toEqual(1);
});
it('should have proper props for password field', () => {
expect(container.find('Password').props()).toEqual({
onChange: expect.any(Function),
value: '',
});
});
it('should have a submit button', () => {
expect(container.find('Button').length).toEqual(1);
});
it('should have proper props for submit button', () => {
expect(container.find('Button').props()).toEqual({
disabled: true,
onClick: expect.any(Function),
});
});
});
To test the state updates like Alex mentioned I tested for sideeffects:
it('should set the password value on change event with trim', () => {
container.find('input[type="password"]').simulate('change', {
target: {
value: 'somenewpassword ',
},
});
expect(container.find('input[type="password"]').prop('value')).toEqual(
'somenewpassword',
);
});
but to test the lifecycle hooks I still use mount instead of shallow as it is not yet supported in shallow rendering.
I did seperate out the methods that aren't updating state into a separate utils file or outside the React Function Component.
And to test uncontrolled components I set a data attribute prop to set the value and checked the value by simulating events. I have also written a blog about testing React Function Components for the above example here:
https://medium.com/#acesmndr/testing-react-functional-components-with-hooks-using-enzyme-f732124d320a
Currently Enzyme doesn't support React Hooks and Alex's answer is correct, but looks like people (including myself) were struggling with using setTimeout() and plugging it into Jest.
Below is an example of using Enzyme shallow wrapper that calls useEffect() hook with async calls that results in calling useState() hooks.
// This is helper that I'm using to wrap test function calls
const withTimeout = (done, fn) => {
const timeoutId = setTimeout(() => {
fn();
clearTimeout(timeoutId);
done();
});
};
describe('when things happened', () => {
let home;
const api = {};
beforeEach(() => {
// This will execute your useEffect() hook on your component
// NOTE: You should use exactly React.useEffect() in your component,
// but not useEffect() with React.useEffect import
jest.spyOn(React, 'useEffect').mockImplementation(f => f());
component = shallow(<Component/>);
});
// Note that here we wrap test function with withTimeout()
test('should show a button', (done) => withTimeout(done, () => {
expect(home.find('.button').length).toEqual(1);
}));
});
Also, if you have nested describes with beforeEach() that interacts with component then you'll have to wrap beforeEach calls into withTimeout() as well. You could use the same helper without any modifications.
Instead of isLoginDisabled state, try using the function directly for disabled.
Eg.
const renderSigninForm = () => (
<>
<form>
<Email
isValid={validateEmail(email)}
onBlur={handleEmailChange}
/>
<Password
onChange={handlePasswordChange}
/>
<Button onClick={handleSubmit} disabled={(password.length < 8 || !validateEmail(email))}>Login</Button>
</form>
</>);
When I was trying similar thing and was trying to check state(enabled/disabled) of the button from the test case, I didn't get the expected value for the state. But I removed disabled={isLoginDisabled} and replaced it with (password.length < 8 || !validateEmail(email)), it worked like a charm.
P.S: I am a beginner with react, so have very limited knowledge on react.

Mock a dependency of a React component using Jest

I have a react component (CreateForm). The React component depends on a module (Store). The CreateForm has a Cancel button. On clicking the cancel button, the handleCancel function of the Store module should be called.
I wrote a test unsuccessfully using Jest:
test.only('should handle cancel button click', () => {
jest.mock('../../src/store');
const store = require('../../src/store');
const wrapper = shallow(<CreateForm />);
const cancelButton = wrapper.find('button').at(1);
cancelButton.simulate('click');
expect(store.default.handleCancel).toBeCalled();
});
The test failed. The mock function did not get called and the test failed. Does the react component not get this version of the mock? If so, how do I fix the test? Thanks.
My CreateForm component looks something like the below:
import Store from './store';
render() {
return (
<Panel>
<FormControls />
<button onClick={Store.create}>Create</button>
<button onClick={Store.handleCancel}>Cancel</button>
</Panel>
);
}
A second improvised test that works for me is shown below.
test.only('should handle cancel button click', () => {
const store = require('../../src/store').default;
const cancel = store.handleCancel;
store.handleCancel = jest.fn();
const wrapper = shallow(<CreateForm />);
const cancelButton = wrapper.find('button').at(1);
cancelButton.simulate('click');
expect(store.handleCancel).toBeCalled();
store.handleCancel = cancel;
});
The above test works. I am manually mocking the function, doing the test and restoring the function back to its original after the test. Is there a better way or Jest way of writing the above test? Thanks.
This is how I have managed to spy on imported functions using Jest.
Import everything that is imported in the file you're testing.
Mock it in the beforeEach, you can use more complex mocking if you need to return values or whatever.
In the afterEach call jest.clearAllMocks() to reset all the functions to normal to stop any mocking falling through to other tests.
Putting it all together it looks something like this.
import shallow from 'enzyme'
import * as Store from './Store' // This should be the actual path from the test file to the import
describe('mocking', () => {
beforeEach(() => {
jest.spyOn(Store, 'handleCancel')
jest.spyOn(Store, 'create')
})
afterEach(() => {
jest.clearAllMocks();
})
test.only('should handle cancel button click', () => {
const wrapper = shallow(<CreateForm />);
const cancelButton = wrapper.find('button').at(1);
cancelButton.simulate('click');
expect(Store.handleCancel).toBeCalled();
})
})
Also, in case you need to mock a default import you can do so like this. jest.spyOn(Store, 'default')
You forgot to tell jest how to mock the store module, in your case it is just undefined.
const store = require('../../src/store');
jest.mock('../../src/store', () =>({
handleCancel: jest.fn()
}));
test.only('should handle cancel button click', () => {
const wrapper = shallow(<CreateForm />);
const cancelButton = wrapper.find('button').at(1);
cancelButton.simulate('click');
expect(store.default.handleCancel).toBeCalled();//I'm not sure about the default here
});
With this solution you tell jest to mock the store with an object that has the handleCancel function which is a jest spy. On this spy you can then test that it was called.

Resources