I want to unit test the following element, especially the onClick function, But have no idea how to do the unit testing
const baseURL = "/Security/Users/";
return (
<div>
<div className="flex-between">
<button
className="bt-save"
onClick={() => history.push(baseURL + "Add")}
>
Add
</button>
</div>
</div>
);
this is related to react unit testing using jest. From this, I want to unit test the Add Button onClick function.
Here is my approach to unit test this function
it('Should run the callback function when clicked', async () => {
const onClick = jest.fn(baseURL + "Add")
render(<button push={onClick}> Add </button>)
const addButton = screen.getByText('Add')
await userEvent.click(addButton)
expect(onClick).toHaveBeenCalled()
})
I'm getting the following error when I'm trying to do the testing.
I'm getting this result on the console
Can anyone help me understand this onClick function unit testing
You can accept an event callback as a prop of the component:
<button
className="bt-save"
onClick={() => props.onAdd()}
>
Then you can unit test using React Testin Library:
it("Should call onSave callback when clicking Add", async () => {
const onAddMock = jest.fn();
render(<MyComponent onAdd={onAddMock} />);
await userEvent.click(screen.getByText("Add"));
expect(onAddMock).toHaveBeenCalled();
}
Related
I am trying to test a hidden file inputs click using Jest-enzyme, for the following component.
const UploadField = ({handleFileUpload}) => {
const hiddenFileInputRef = React.useRef(null);
const handleChange = (event) => {
handleFileUpload(event.target.files[0]);
};
const handleClick = () => {
hiddenFileInputRef.current.click();
};
return (
<>
<input
type="file"
ref={hiddenFileInputRef}
onChange={handleChange}
style={{display: 'none'}}
/>
<div
onClick={handleClick}
className="upload-button-container">
upload file
</div>
</>
);
};
i tried the following test :
it('should call handle click of hidden fileinput on click of div', () => {
const useRefSpy = jest
.spyOn(React, 'useRef')
.mockReturnValueOnce({current: {click: jest.fn()}});
let divWrapper = wrapper.find(
'.upload-button-container'
);
divWrapper.simulate('click');
expect(useRefSpy).toBeCalledTimes(1);
});
It gives an error "TypeError: Cannot read property 'click' of null" . what am i doing wrong here?
Try to check if you were able find the right div by logging wrapperDiv.html() or debugging
I think you might have to pass the event like in this post. Simulating a Div Click on Enzyme and React
I'm trying to test a component that have a <Switch/> (using Material-UI) and a <TextField/>. When user clicks in this Switch, it makes another field (<TextField/>) enable/disabled. The code works well, but my test using JEST always fails.
...
export function MyComponent(): JSX.Element{
const [enableExpiresOn, setEnableExpiresOn] = useState(false);
...
function handleAccountExpiresOn(e: React.ChangeEvent<HTMLInputElement>) {
setEnableExpiresOn(e.target.checked);
}
return (
<form ...>
...
<div>
<FormControlLabel
control={
<Switch
id="account-expires"
onChange={handleAccountExpiresOn}
name="account-expires"
color="primary"
checked={enableExpiresOn}
/>
}
label={t("Account Expires On")}
/>
</div>
<div>
<TextField
id="account-expires-on"
label={t("Account Expires On")}
type="datetime-local"
variant="outlined"
required={enableExpiresOn}
InputLabelProps={{
shrink: true,
}}
disabled={!enableExpiresOn}
/>
</div>
...
</form>
);
}
And in my JEST test file, I have the follow (as you can see, I'm trying to simulate a click in the Switcher):
describe("Should render correctly", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(<MyComponent />);
});
...
fit("Should enable Account Expires On when switcher is on/checked", async () => {
wrapper.find("#account-expires").at(0).simulate("click");
expect(
wrapper.find("#account-expires-on").get(0).props.disabled
).toEqual(false);
});
However, Jest gives me an error in this expectation, saying that it have received true instead false.
I tried to put a setTimeout around the expectation, hoping that it would solve the problem because maybe we need to wait for the click to have effect, but it doesn't work.
I'm newer with JEST and React Hooks. So, I don't know if it is the best way to test what this behaviour or there is a better way.
Used Technologies
React Hooks
NextJS
Material UI
TypeScript
Jest
Enzyme
useState is async, jest check assertion before element get disabled.
Can try:
describe("Should render correctly", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(<MyComponent />);
});
...
fit("Should enable Account Expires On when switcher is on/checked", () => {
jest.useFakeTimers()
wrapper.find("#account-expires").at(0).simulate("click");
jest.runAllTimers()
// or possibly
// await new Promise(resolve => setTimeout(resolve, 0))
expect(
wrapper.find("#account-expires-on").get(0).props.disabled
).toEqual(false);
});
And can google it
UPD
Can try to simulate onChange event or wrapper.find("#account-expires").at(0).props().onChange(); from here
Similar troubles with testing-library https://github.com/mui-org/material-ui/issues/17697
I'm attempting to test a React component similar to the following:
import React, { useState, useEffect, useRef } from "react";
export default function Tooltip({ children }) {
const [open, setOpen] = useState(false);
const wrapperRef = useRef(null);
const handleClickOutside = (event) => {
if (
open &&
wrapperRef.current &&
!wrapperRef.current.contains(event.target)
) {
setOpen(false);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("click", handleClickOutside);
};
});
const className = `tooltip-wrapper${(open && " open") || ""}`;
return (
<span ref={wrapperRef} className={className}>
<button type="button" onClick={() => setOpen(!open)} />
<span>{children}</span>
<br />
<span>DEBUG: className is {className}</span>
</span>
);
}
Clicking on the tooltip button changes the state to open (changing the className), and clicking again outside of the component changes it to closed.
The component works (with appropriate styling), and all of the React Testing Library (with user-event) tests work except for clicking outside.
it("should close the tooltip on click outside", () => {
// Arrange
render(
<div>
<p>outside</p>
<Tooltip>content</Tooltip>
</div>
);
const button = screen.getByRole("button");
userEvent.click(button);
// Temporary assertion - passes
expect(button.parentElement).toHaveClass("open");
// Act
const outside = screen.getByText("outside");
// Gives should be wrapped into act(...) warning otherwise
act(() => {
userEvent.click(outside);
});
// Assert
expect(button.parentElement).not.toHaveClass("open"); // FAILS
});
I don't understand why I had to wrap the click event in act - that's generally not necessary with React Testing Library.
I also don't understand why the final assertion fails. The click handler is called twice, but open is true both times.
There are a bunch of articles about limitations of React synthetic events, but it's not clear to me how to put all of this together.
I finally got it working.
it("should close the tooltip on click outside", async () => {
// Arrange
render(
<div>
<p data-testid="outside">outside</p>
<Tooltip>content</Tooltip>
</div>
);
const button = screen.getByRole("button");
userEvent.click(button);
// Verify initial state
expect(button.parentElement).toHaveClass("open");
const outside = screen.getByTestId("outside");
// Act
userEvent.click(outside);
// Assert
await waitFor(() => expect(button.parentElement).not.toHaveClass("open"));
});
The key seems to be to be sure that all activity completes before the test ends.
Say a test triggers a click event that in turn sets state. Setting state typically causes a rerender, and your test will need to wait for that to occur. Normally you do that by waiting for the new state to be displayed.
In this particular case waitFor was appropriate.
I built a react app where I'm trying to get myself more confortable with testing, yet I'm quite stuck trying to test a button from my component. The documentation is very vague and I have not found any solution to my case.
The onClick method is simply calls a handleClick method like so at Body.js:
const handleClick = () => {
console.log('handling click')
}
return (
<div className="container">
I'm the body
{posts &&
posts.map((post, i) => {
return (
<div key={i}>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
})}
<Button onClick={handleClick}>Get the posts</Button> // this button
</div>
);
};
I'm using mock function in my test like so:
it('button triggers handleClick', () => {
const fn = jest.fn();
let tree = create(<Body onClick={fn} />);
// console.log(tree.debug());
// simulate btn click
const button = tree.root.findByType('button');
button.props.onClick();
// verify callback
console.log(fn.mock);
expect(fn.mock.calls.length).toBe(1);
});
But I cannot assert that the click was made.
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
The handleClick method is working since I'm getting the desired output with console.log when I run the test.
console.log
handling click // it works!
at Object.onClick (src/components/Body.js:9:15)
console.log
{ calls: [], instances: [], invocationCallOrder: [], results: [] } // fn.mocks log
I'd appreciate any help.
Fernando,
I am new to testing in react and am building a simple todo application. I am struggling to mock the delete function in the lists component.
My test looks like this at the moment
it('deletes an item from the list', () => {
const deleteItem = jest.fn();
const items = [{body: "New note", id: "12345"}]
const textEl = wrapper.find("#text-output p")
wrapper = shallow(<ListItems list={items} deleteItem={deleteItem}/>)
wrapper.find('#delete').simulate('click')
expect(textEl.text()).toContain("//Should be empty")
})
currently the when I run the tests the error reads..
Expected substring: "//Should be empty"
Received string: "New note X"
The application works fine, but I am not mocking the delete function correctly in my test as a "New Note" is still being passed through. What am I doing wrong?
just in case it helps, here is the file I am testing..
function ListItems(props) {
const list = props.list
const displayItems = list.map((item, index) =>
{
return <div id='text-output' key={index}>
<p>
{item.body }
<button
id="delete"
onClick={ () => props.deleteItem(item.id)}>
X
</button>
</p>
</div>
})
return(
<div>
<h1>This is the list</h1>
{displayItems}
</div>
)
}
any help would be great!
In your test since you mocked the deleteItem callback it doesn't actually delete an item from the items object passed to the list prop. Also, const textEl = wrapper.find("#text-output p") before wrapper = shallow(<ListItems list={items} deleteItem={deleteItem}/>) probably also won't work as expected.
You can however instead assert the deleteItem mock was called with the specified item id. TBH, that is what the test should be anyway since that component under test doesn't appear to have any logic other than rendering a list of items and attaching an onClick callback.
.toHaveBeenCalledWith
it('should invoke deleteItem callback on button click with item id', () => {
const deleteItemMock = jest.fn();
const items = [{body: "New note", id: "12345"}];
wrapper = shallow(<ListItems list={items} deleteItem={deleteItemMock}/>);
wrapper.find('#delete').simulate('click');
expect(deleteItemMock).toHaveBeenCalledWith("12345");
});