How to test a function that has been passed by props - reactjs

If I have a component like this
import React from "react";
const GettingStarted = ({ object }) => <Button onPress={() => object.next()} />;
How would you test with jest that object.next() gets called inside the component?

The basic idea would be to spy on the function being passed to the onPress. Then, you would simulate an onPress event on the button and check that the spied upon function was called with any parameters, etc. Then you would test the actual output of the function. So, for example, if the function changes the text in the button from 'Click Me' to 'Clicked!', you would assert on the first text property before the click and then check the updated one.
Example with Jest:
const onPressSpy = jest.fn();
const gettingStartedButton = shallow(<GettingStarted object={onPressSpy} />);
expect(gettingStartedButton.find('button').children().text()).toBe('Click Me!');
gettingStartedButton.find('button').simulate('press');
expect(onPressSpy).toHaveBeenCalled();
expect(gettingStartedButton.find('button').children().text()).toBe('Clicked!');

Related

Why is my jest "onClick" test not firing the mocked function?

I am using jest to test whether a button is clickable or not. The button is a Material-UI component, but is itself working exactly as intended on the frontend; only the test is failing.
The code for the button:
import { Button as BaseButton } from '#mui/material';
export function Button({ children, isDisabled = false, onClick }) {
return (
<BaseButton disabled={isDisabled} onClick={onClick}>
{children}
</BaseButton>
);
}
And the code for the test:
import { Button } from './Button';
import { render, screen } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import userEvent from '#testing-library/user-event';
it('should be clickable', () => {
const text = 'Hello World!';
const testFunction = jest.fn();
render(<Button onClick={() => testFunction}>{text}</Button>);
userEvent.click(screen.getByText('Hello World!'));
expect(testFunction).toHaveBeenCalledTimes(1);
});
I have other tests in the same file which are passing, so I don't think it's some type of import issue. The button is definitely rendering properly on the test page, but can't be clicked. I have also tried updating the button locator to userEvent.click(screen.getByRole('button')); to no avail.
I am getting the following error from the test file, which I believe is caused by the click test:
(node:30870) UnhandledPromiseRejectionWarning: Error: Unable to perform pointer interaction as the element has or inherits pointer-events set to "none".
However, the button component does not even have a pointer-events property. The child span element, which forms the button label, does have pointer-events: none, but the parent element shouldn't be inheriting that from a child. In addition, I tested the jest.fn() on a normal, html element and got the exact same error.
You're not actually calling the function, just returning it from the onClick callback (which doesn't do anything)
onClick={() => testFunction}
Is the same as:
onClick={() => {
return testFunction;
}}
That is just returning testFunction. To call it you want to:
onClick={() => testFunction()}
Or, if you want to pass it directly so it get's called you can do:
onClick={testFunction}
Then, when the click fires it will call the passed function. But then it will receive the event arguments etc, which you might not want.

Test React Form internal submit

I have this component:
function Form() {
const [value, setValue] = useState('');
const hanleSubmit = (ev) => {
ev.preventDefault();
console.log('submit', value);
}
return (
<form onSubmit={props.onSubmit}>
<input type="text" value={value} onChange={e => setValue(e.target.value)} />
<button type="submit">Add</button>
</form>
);
}
and the following test:
import React from "react";
import { render, fireEvent } from "react-testing-library";
import Form from "./Form";
it("submits", () => {
const onSubmit = jest.fn();
const { getByText } = render(<Form />);
fireEvent.click(getByText("Add"));
expect(onSubmit).toHaveBeenCalled(); // test fail with 0 called
});
I know I gotta to pass this mock function as prop.
But I'd like to know if exist a way to test internal function like handleSubmit without pass as prop?
I'm using react-testing-library.
You can use jest.spyOn(console, 'log') and check the handleSubmit is triggered indirectly. In fact, handleSubmit should have some more meaningful logic, not just console.log(). We should test the meaningful logic. But for the code you posted, then only we can do is above.
You should test the behavior of the component rather than an implementation detail.
The behavior of the component is: when you trigger the event handler, some states may change, the component will re-render, UI structure change, assert UI changes.
The implementation detail is: assert some function is called or not. This makes test cases vulnerable.
Test behavior or function, you don't need to pay attention to implementation details, regardless of the implementation details, we only test the final result.
If your component is just a presentation component with no state, just perform a Snapshot test to check the UI hierarchy.
If there are states, you need to test different states of the component.
If the state is promoted to the parent, then you test the parent and the state of the child on the UI when the parent state changes
Passing the mock onSubmit as a prop is a simple and common way.
If you want to handle some logic in Form component and call the onSubmit function passed from parent component in the same time.
You should write it like this:
const hanleSubmit = (ev) => {
ev.preventDefault();
// some logic for Form component itself, you should test these logic when you test Form component rather than `props.onSubmit()`.
console.log('submit', value);
// Parent component only care about the submitted value.
props?.onSubmit(value)
}

Not simulating 'change' using enzyme

I have a Reactjs component and it has one button and a date-range picker.
I want to simulate onclick and onchange events of button and picker respectively.
I'm able to simulate onclick of the button. but on change of datepicker is not working
I have tried this
headerComponent.find(`#prev_button`).at(1).simulate("click");
headerComponent.find(`#dropdown`).at(1).simulate("change", { value: "t" });
please see this sandbox click here for full code and test file
Based on Enzyme documentation you make a mistake on your second argument on simulate function.
To simulate changes on the input, you should change it like this :
headerComponent.find(`#dropdown`).at(1).simulate("change", { target: { value: "t" } });
Testing with enzyme is tricky. You should try not to test dependencies because you trust those are already tested. Having said that, you could shallow render instead of mounting and look for the RangePicker component in the shallow tree, get the handler you are passing in the onChange prop and call it manually, then check the callback prop you pass to your component is called with the expected value.
describe.only("test", () => {
it("should render", () => {
const callBackToSetDates = jest.fn();
const callBackToSetFilter = jest.fn();
const wrapper = shallow(
<Header
{...headerProps1}
callBackToSetDates={callBackToSetDates}
callBackToSetFilter={callBackToSetFilter}
/>
);
const rangePickerOnChange = wrapper.find("RangePicker").prop("onChange");
rangePickerOnChange("someValue");
expect(callBackToSetDates).toHaveBeenCalledWith("someValue");
});
});
the purpose is to test only the logic you add inside your component, i.e., you transform the value you get from the RangePicker to something else
<RangePicker
...
onChange={(value) => {
callBackToSetDates(`I'm transforming ${value}`);
}}
/>
and in your test
rangePickerOnChange("someValue");
expect(callBackToSetDates).toHaveBeenCalledWith("I'm transforming someValue");
you can see it working here https://codesandbox.io/s/cool-rosalind-uec6t?file=/src/tests/index.test.js
If you really want to keep testing what the actual user sees, you'll need to fire the events that the user does when using the component. In this case: you need to click the input, look for a date, click it, then click another date to completely fire the onChange event of the RangePicker component. You might look at how antd test it and copy the necessary jest configuration they have to mock some DOM APIs

Testing onChange event with Jest

According to my code coverage, I need to test the function called in my onChange event. This is actually where I update my functional component's state using the useState hook.
Here is my component :
const Component:React.FC<{}> = () => {
const {value, setState} = useState('');
return(
<View>
<CustomComponent
onChange={(value) => setState(value)}
/>
</View>
)
}
The customComponent is a component derived from the React Input component.
When text is changed inside of it, it calls the onChange function passed as its prop with the text. This is how it comes up to the parent Component which sets the value of the text input in its state as displayed above.
My code coverage returns this analysis :
onChange={//(value) => setState(value)//}
Where the code between the // has to be covered. I don't really understand how I can cover this. First thought was use mock functions, but I can't seem to find how to mock it to the onChange event since I don't pass anything as prop to the main Component.
After a few tests, I finally understood that the coverage wasn't asking for the actual test of the onChange function but actually the value that is evaluated. Therefore, here is what I am doing:
Fetching the TextInput Child component
Changing its Text
Evaluating what it renders
I am using #testing-library/react-native here because it makes selecting tree components easier with the use of accessibilityLabel for example (It actually made me understand the importance of that prop).
Here is what a test looks like:
describe('Testing useState functions', () => {
test('test', () => {
//Rendering the component and its tree
const { container, getByLabelText } = render(<SignupView />);
//Extracting the child, username_input component with his accessibilityLabel
const username_input = getByLabelText('username_input');
const email_input = getByLabelText('email_input');
//Fire a native changeText event with a specific value
fireEvent.changeText(username_input, 'doe');
fireEvent.changeText(email_input, 'doe#joe.com');
//Checking the rendered value
expect(username_input.props.value).toEqual('doe');
expect(email_input.props.value).toEqual('doe#joe.com');
});
});

Why is my button undefined in my test?

My test:
describe('button component', () => {
it('should toggle off when clicked', () => {
let component;
component = ReactTestUtils.renderIntoDocument(<Search />);
let searchbtn = ReactTestUtils.findRenderedDOMComponentWithTag(component, 'button');
ReactTestUtils.Simulate.click(searchbtn);
console.log(searchbtn, 'search button***'); //UNDEFINED
expect(searchbtn.calledOnce).to.equal(false);
})
});
This is my search component:
render() {
return (
<div className="search">
<button className="searchButton" onClick={this.handleSearch}>{this.state.on ? 'ON' : 'OFF'}</button>
</div>
);
}
Do I need to spy on it or mock it? or is there a better way to test buttons in react?
Since you have added enzyme tag I will answer using enzyme.
It can be tested very easily via shallow rendering -
const searchWrapper = shallow(<Search />);
const button = searchWrapper.find('button').first();
When you simulate click event on button the onClick handler provided via onClick prop which is handleSearch in your case will be called.
So if you are setting some state based on the onClick function call corresponding ui changes based on the state changes can be compared or checked if changes were reflecting correctly in the dom.
or
if you just want to check if method was called or not by using a fake method of similar name -
const onButtonClick = sinon.spy();
expect(onButtonClick.calledOnce).to.equal(false);
button.setProps({ onClick:onButtonClick});
button.simulate('click');
expect(onButtonClick.calledOnce).to.equal(true);

Resources