Test onClick with Jest Mock Function - reactjs

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,

Related

How to do the unit testing of history.push function?

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

How to write unit tests for a function call in a div in enzyme and jest?

I have a function call in my react component. I need to write the unit tests for it.
Here is my function definition:
const renderError = () =>{
const {isError, errorMessage, errorFromWhere} = error;
if(isError && errorFromWhere === "fromDraft"){
return (
<div className="action-button-error">
<Notification type={"warning"} text={errorMessage} />
</div>
);
}
return null;
};
Here is the return statement:
return (
<div className="action-buttons-wrapper">
<div className="right-section">
{
renderError()
}
</div>
</div>);
I need to test the renderError() method. I tried it by using shallow rendering and then tried to access the method by wrapper.instance(). But this is returning null. Here is the UT that I tried:
it("testing the render error function", ()=>{
const wrapper=shallow(<ActionButtons {...defaultProps}/>);
const spy=jest.spyOn(wrapper.instance(), "renderError");
expect(spy).toHaveBeenCalled();
console.log(wrapper.instance());
});
This console.log prints null. I am not able to figure how to check this method. Anyone who can guide me here?
you've to spy on the targetted function before initializing it on the wrapper var.
try this:
it("testing the render error function", () => {
// register the spy first
const spy = jest.spyOn(ActionButtons.prototype, "renderError")
// then mount it as the `wrapper`
const wrapper=shallow(<ActionButtons {...defaultProps}/>);
expect(spy).toHaveBeenCalled();
});

Test useRef's currents' properties using jest-enzyme

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

Test document listener with React Testing Library

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.

Mocking a simple function in react.js, jest, enzyme

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

Resources