Mock function doesn't get called when inside 'if' statement - React app testing with jest and enzyme? - reactjs

I am writing a test case for my react app and I'm trying to simulate a button click with a mock function. I'm passing the mock function as a prop and I'm calling the function inside an 'if' statement but the mock function doesn't get called and the test fails but if i call the function without the 'if' statement it gets called and the test passes. Why is this happening?
Form.js
const Form = ({ text, incompleteList, setIncompleteList }) => {
const submitTodoHandler = (e) => {
e.preventDefault()
if (text !== '') {
setIncompleteList([...incompleteList, { name: text, id: Math.random() * 1000 }])
}
}
return (
<form action='' autoComplete='off'>
<button type='submit' className='todo-button' onClick={submitTodoHandler}>
add
</button>
</form>
)
}
export default Form
Form.test.js
import Enzyme, { shallow, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Form from '../components/Form'
Enzyme.configure({ adapter: new Adapter() })
test('Form calls setIncompleteList prop on add button onClick event', () => {
const mockfn = jest.fn()
const wrapper = mount(<Form setIncompleteList={mockfn} />)
wrapper.find('button').simulate('click')
expect(mockfn).toHaveBeenCalled()
})
I'm using react 16.

The problem was I did not pass the 'text' props to the form component and the comparison failed to take place that's why the mock doesn't get called and the test failed.
<Form text='mock' setIncompleteList={mockfn} />

Pass value and incompleteList while mounting the component
test('Form calls setIncompleteList prop on add button onClick event', () => {
const mockfn = jest.fn()
const wrapper = mount(<Form text='mock'
incompleteList={[{name: 'sarun', id: 1001}]} setIncompleteList={mockfn} />)
wrapper.find('button').simulate('click')
expect(mockfn).toHaveBeenCalled()
})
you can also set a default value for incompletelist like below so that no need to pass incompletelist while mounting the component,
const Form = ({ text, incompleteList = [], setIncompleteList }) => {
}

Related

How can I test an input with Jest

I've been trying to figure out how to test different input methods but since I am new to this test methodology, I cannot get even close to the answer. Here is what I have:
const App = (props) => {
const newGame = props.newGame;
const [typeracertext, setTyperacertext] = useState(props.typeracertext);
const [wholeText, setWholeText] = useState("");
const onChange = (e) => {
//here I have code that read the input and is comparing it with variable - typeracertext and if so, it sets the property wholeText to that value
};
return (
<input ref={(node) => this.textInput = node} placeholder="Message..." onChange={onChange}></input>
);
}
so what I am trying to figure out is a test that should set the typeracertext to a certain value (for example "This is a test), and set the input value to "This" so if it passes the onChange() check it should set wholeText to "This". I hope that makes sense.
This is the best I could get and I don't have an idea what should I write on "expect".
test('Test the input value', () => {
const node = this.textInput;
node.value = 'This';
ReactTestUtils.Simulate.change(node);
expect()
});
Since this is a react app, I'll advice you take advantage of react testing library to make this easy
import React from 'react';
import { fireEvent, render, screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
// In describe block
test('Test input component', () => {
const onChange = jest.fn();
render(<InputComponent onChange={onChange} data-test-id="input" />);
const input = screen.getByTestId('input');
fireEvent.change(input, { target: { value: 'a value' } });
// You can also do this with userEvent
userEvent.type(input, 'test')
// Check if change event was fired
expect((input as HTMLInputElement).onchange).toHaveBeenCalled();
});
See documentation here

Testing a CallBack function in a function which get triggered forButton Onclick using jest

I have a React child component which has button
export function Banner({argumentSetter}){
function handleOnClick(){
argumentSetter(argument.READ);
}
return(
<div>
<Button onClick={handleOnClick}>
<Icon name="delete" type="filled">
Discard
</Icon>
</Button>
</div>
)
}
And I have my argumentSetter in my parent component defined as following,
const [argument,setArgument] = useState<Argument>(argument.EDIT);
argumentSetter = useCallBack((val)=>{
setArgument(val);
},[argument]);
return(
<div>
<Banner argumentSetter={argumentSetter}/>
</div>
)
How to get 100% test coverage using jest.
To test the banner, your code should be like the following
import React from "react";
import { mount } from "enzyme";
import { Banner } from "./Banner.js";
import { argument } from "./arguments.js";
it("Button click leads to argument.READ", async () => {
let promiseResolve = null;
const argPromise = new Promise((resolve) => {
promiseResolve = resolve;
});
const argumentSetter = (arg) => promiseResolve(arg);
const banner = mount(<Banner argumentSetter={argumentSetter} />);
banner.find("button").simulate("click");
const newArg = await argPromise;
expect(newArg).toEqual(argument.READ);
});
Explanation:
We create an externally fulfillable promise variable, called argPromise which will resolve when promiseResolve is called, which is called when the argumentSetter is called. Hence, when the button click is simulated, it will resolve the updated argument to newArg variable (which should be argument.READ), and hence you can test if it matches your expectation.
This should hence cover all lines of your Banner component during testing.

How to fire and test a real paste event (not simulated by calling the prop) in Jest and Enzyme

I'm trying to unit test a very simple feature in a React app where I'm blocking the user from pasting into a textarea by adding an event.preventDefault() in the event handler, like so:
function handlePaste(event) {
event.preventDefault();
}
// ... pass it down as props
<TextareaComponent onPaste={handlePaste} />
The problem I'm having is that every method I've found of dispatching events in Jest or Enzyme just "simulates" the event by getting the function passed to the onPaste prop and calling it directly with a mock event object. That's not what I'm interested in testing.
Ideally I want to do something like this, testing that the actual value of the input hasn't changed after pasting:
const wrapper = mount(<ParentComponent inputValue="Prefilled text" />);
const input = wrapper.find(TextareaComponent);
expect(input.value).toEqual("Prefilled text")
input.doAPaste("Pasted text")
expect(input.value).not.toEqual("Pasted text")
expect(input.value).toEqual("Prefilled text")
But haven't been able to find a method that works. Any help would be appreciated!
Since you're just testing against a synthetic event (and not some sort of secondary action -- like a pop up that warns the user that pasting is disabled), then the easiest and correct solution is to simulate a paste event, pass it a mocked preventDefault function, and then assert that the mocked function was called.
Attempting to make assertions against a real paste event is pointless as this a React/Javascript implementation (for example, making assertions that a callback function is called when an onPaste/onChange event is triggered). Instead, you'll want to test against what happens as a result of calling the callback function (in this example, making assertions that event.preventDefault was called -- if it wasn't called, then we know the callback function was never executed!).
Working example (click the Tests tab to run the assertions):
To keep it simple, I'm just asserting that the input is initially empty and then only updates the value if an onChange event was triggered. This can very easily be adapted to have some sort of passed in prop influence the default input's value.
App.js
import React, { useCallback, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
const handleChange = useCallback(
({ target: { value } }) => setValue(value),
[]
);
const handlePaste = useCallback((e) => {
e.preventDefault();
}, []);
const resetValue = useCallback(() => {
setValue("");
}, []);
const handleSubmit = useCallback(
(e) => {
e.preventDefault();
console.log(`Submitted value: ${value}`);
setValue("");
},
[value]
);
return (
<form onSubmit={handleSubmit}>
<label htmlFor="foo">
<input
id="foo"
type="text"
data-testid="test-input"
value={value}
onPaste={handlePaste}
onChange={handleChange}
/>
</label>
<br />
<button data-testid="reset-button" type="button" onClick={resetValue}>
Reset
</button>
<button type="submit">Submit</button>
</form>
);
};
export default App;
App.test.js
import React from "react";
import { configure, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App from "./App";
configure({ adapter: new Adapter() });
const value = "Hello";
describe("App", () => {
let wrapper;
let inputNode;
beforeEach(() => {
wrapper = mount(<App />);
// finding the input node by a 'data-testid'; this is not required, but easier
// when working with multiple form elements and can be easily removed
// when the app is compiled for production
inputNode = () => wrapper.find("[data-testid='test-input']");
});
it("initially displays an empty input", () => {
expect(inputNode()).toHaveLength(1);
expect(inputNode().props().value).toEqual("");
});
it("updates the input's value", () => {
inputNode().simulate("change", { target: { value } });
expect(inputNode().props().value).toEqual(value);
});
it("prevents the input's value from updating from a paste event", () => {
const mockPreventDefault = jest.fn();
const prefilledText = "Goodbye";
// updating input with prefilled text
inputNode().simulate("change", { target: { value: prefilledText } });
// simulating a paste event with a mocked preventDefault
// the target.value isn't required, but included for illustration purposes
inputNode().simulate("paste", {
preventDefault: mockPreventDefault,
target: { value }
});
// asserting that "event.preventDefault" was called
expect(mockPreventDefault).toHaveBeenCalled();
// asserting that the input's value wasn't changed
expect(inputNode().props().value).toEqual(prefilledText);
});
it("resets the input's value", () => {
inputNode().simulate("change", { target: { value } });
wrapper.find("[data-testid='reset-button']").simulate("click");
expect(inputNode().props().value).toEqual("");
});
it("submits the input's value", () => {
inputNode().simulate("change", { target: { value } });
wrapper.find("form").simulate("submit");
expect(inputNode().props().value).toEqual("");
});
});

How can I test a click event which changes a useState state with enzyme?

I have the following component:
import React, { useState } from "react";
import { Button, ThirteenBold } from "#selfdecode/sd-component-library";
import { PlayIcon } from "assets/icons";
import { TourButtonProps } from "./interfaces";
import { WelcomeVideoModal } from "../../modals/welcome-video-modal";
/**
* The tour button.
*/
export const TourButton: React.FC<TourButtonProps> = (props) => {
const [isIntroVideoShowing, setIsIntroVideoShowing] = useState(false);
return (
<>
<WelcomeVideoModal
isOpen={isIntroVideoShowing}
onClickX={() => setIsIntroVideoShowing(false)}
data-test="tour-button-welcome-video"
/>
<Button
{...props}
width={["max-content"]}
variant="tour"
onClick={() => setIsIntroVideoShowing(true)}
data-test="tour-button"
>
<ThirteenBold
mr={["12px"]}
color="cl_blue"
width={["max-content"]}
letterSpacing={["1px"]}
display={["none", "block"]}
textTransform="uppercase"
>
welcome tour
</ThirteenBold>
<PlayIcon style={{ height: "30px", fill: "#4568F9" }} />
</Button>
</>
);
};
And the test coverage report is complaining that I am not testing both of the onClick events, which change the state.
I've tried two approaches, and both fail.
Approach one was to mock the useState and see if it gets called as I'd have expected it.
This was the test I tried:
const setState = jest.fn();
const useStateMock: any = (initState: any) => [initState, setState];
jest.spyOn(React, "useState").mockImplementation(useStateMock);
const button = wrapper.find(`[data-test="tour-button"]`);
expect(button).toHaveLength(1);
button.simulate("click");
expect(setState).toHaveBeenCalled();
This shouldn't even be the final test, as it doesn't check what was the valuee it was called with, but still, it failed because useState wasn't even called.
The second approach I've tried was to check the prop value on this component:
<WelcomeVideoModal
isOpen={isIntroVideoShowing}
onClickX={() => setIsIntroVideoShowing(false)}
data-test="tour-button-welcome-video"
/>
And this is the test I've tried
test("Check the isIntroVideoShowing changes to true on buton click", () => {
jest.spyOn(React, "useState").mockImplementation(useStateMock);
const button = wrapper.find(`[data-test="tour-button"]`);
const welcomeVideo = wrapper.find(
`[data-test="tour-button-welcome-video"]`
);
expect(button).toHaveLength(1);
expect(welcomeVideo.prop("isOpen")).toEqual(false);
button.simulate("click");
expect(welcomeVideo.prop("isOpen")).toEqual(true);
});
This one failed claiming it was called with false even after the click.
Is there a way to make these work? Or a different approach to cover these?
You need to give wrapper.update for updating the template with state changes after simulating the click event.
test("Check the isIntroVideoShowing changes to true on buton click", () => {
jest.spyOn(React, "useState").mockImplementation(useStateMock);
const button = wrapper.find(`[data-test="tour-button"]`);
const welcomeVideo = wrapper.find(
`[data-test="tour-button-welcome-video"]`
);
expect(button).toHaveLength(1);
expect(welcomeVideo.prop("isOpen")).toEqual(false);
button.simulate("click");
wrapper.update();
expect(welcomeVideo.prop("isOpen")).toEqual(true);
});
Reference - https://enzymejs.github.io/enzyme/docs/api/ShallowWrapper/update.html

Testing if button changes state, or if component appears (React)

I have a component with a button and a form. When button is visible, the form is hidden and the opposite - when we click button it dissapears and form is shown. I would like to test it either with enzyme or testing-library, but all my tests fail.
import React, { useState } from 'react';
import Form from './Form';
const FormComponent = () => {
const [isFormVisible, setFormVisibility] = useState(false);
function toggleFormVisibility() {
setFormVisibility(!isFormVisible);
}
return (
<section>
{!isFormVisible && (
<button
id='toggle-form-button'
data-testid='toggle-form-button'
onClick={toggleFormVisibility}
>
Show form
</button>
)}
{isFormVisible && <Form onCancel={toggleFormVisibility} />}
</section>
);
};
export default FormComponent;
My test:
describe('Form component', () => {
it('should fire toggle form action on button click', () => {
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, 'useState');
useStateSpy.mockImplementation(() => [undefined, setState]);
const component = render(
<Form />
);
const showFormButton = component.getByTestId('toggle-form-button');
Simulate.click(showFormButton);
expect(showFormButton).toBeCalled();
});
});
and another one:
it('should fire toggle form action on button click', () => {
const toggleFormVisibility = jest.fn();
const component = render(
<Form />
);
const showFormButton = component.getByTestId('toggle-form-button');
Simulate.click(showFormButton);
expect(toggleFormVisibility).toBeCalled();
});
It looks like in your tests, you are trying to render the <Form> instead of the <FormComponent>, that might be causing the problem in your test.
Also in your 2nd test, you are not setting up the toggleFormVisibility mocked function with your component, so that wouldn't be invoked at all, the answer above is pretty reasonable, you might want to consider giving that a shot, not sure why it gets downvoted.
testing-library may make this test easier:
import { render, fireEvent } from '#testing-library/react'
render(<Form />);
fireEvent.click(screen.getByLabelText('Show form'));

Resources