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

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

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

onChange antd Input component doesn't setState (hook) properly with list from axios get in React

I'm new to React, been working on it for the past week. I'm trying to make a simple app that has a 'product create' form and a list of products with a search bar (using Input component from antd); in the list I can click on any product to open the details page.
Right now I'm blocked by some not properly working logic or something I miss. When I tried the Input onChange with an Array I created in the code it worked fine, but now that I'm using a mock api (from fakestoreapi.com to be precise), I can't make it work.
ProductsList.tsx
function ProductsList() {
const [list, setList] = useState<Array<Product>>([]);
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => { // I think something is wrong here
ProductService.getAll()
.then((res: any) => {
setList(res.data);
setLoading(false);
})
.catch((e: Error) => console.log(e));
}, []); // tried: 'query' const from state, 'filterList' from state
function onChange(e: React.ChangeEvent<HTMLInputElement>) { // Or here (or both here and in useEffect)
console.log('in onChange');
const filterList: Array<Product> = list.filter((item) =>
item.title.toLowerCase().startsWith(e.target.value.toLowerCase())
);
setList(filterList);
}
return (
<div>
<Spin spinning={loading}>
<List
header={
<Input
type="text"
placeholder="Search product"
allowClear
onChange={onChange}
/>
}
split
dataSource={list}
renderItem={(item) => (
<List.Item key={item.id}>
<Link to={`/products/${item.id}`}>{item.title}</Link>
</List.Item>
)}
></List>
</Spin>
</div>
);
}
export default ProductsList;
I tried adding some dependencies to the useEffect hook, but maybe they were the wrong ones. As I said, with a local array this worked, but now after loading the full list once, when I get to the Input and search something, the list is deleted. I think I spotted the problem in the fact that I don't reset the list to the full one, but I don't actually know how to do that (that's why I'm here). I tried to search something online but except for dependencies, I didn't find something specific to help me.
If needed, here is the ProductService.getAll() function:
function getAll() { // http is axios
return http.get<Array<Product>>(`/products`);
}
I'll be glad to add everything that could be helpful if needed.
const [list, setList] = useState<Array<Product>>([]); // The full list
const [filteredList, setFilteredList] = useState<Array<Product>>([]); // the list you display
function onChange(e: React.ChangeEvent<HTMLInputElement>) { // Or here (or both here and in useEffect)
console.log('in onChange');
const temp: Array<Product> = list.filter((item) => //keep the filter on the full list but only display the filtered list
item.title.toLowerCase().startsWith(e.target.value.toLowerCase())
);
setFilteredList(temp);
}
//the datasource: dataSource={filteredList}

Re-finding a component with Enzyme still doesn't return what should be the correct prop value

I have these two components. One is a modal and another is a button the should open that modal (it is opened by passing true to its isOpen prop.
The modal starts of with isOpen being false, then when the button is clicked, it changes that state to true.
When compiling it woks just fine as intended. In my tests though, it keeps returning false as the value of isOpen even after clicking the button.
These are my two components and the state he handles them:
const [isManagePaymentSourceShowing, setIsManagePaymentSourceShowing] = useState(false);
<ManageSubscriptionModal
isOpen={isManageSubscriptionShowing}
onClickX={closeManageSubscriptionModals}
onClickCancelSubscription={() => setIsSignInAgainShowing(true)}
data-test="amsp-manage-subscription-modal"
/>
<TextCtaButton
fontSize={["13px", "12px"]}
fontWeight="600"
textTransform="uppercase"
onClick={() => {
setIsManagePaymentSourceShowing(true);
console.log("Button was clicked ".repeat(100));
}}
data-test="amsp-manage-payment-source-button"
>
manage payment source
</TextCtaButton>
I've added that console log too and I see that the button does get clicked in the test.
This is the test I am trying to run:
const setup = (userSubscription = userSubscriptionFixture()) => {
return mount(
<Wrapper>
<AccountManagementSubscriptionPresentational
subscriptionProduct={undefined}
userSubscription={userSubscription}
/>
</Wrapper>
);
};
describe("Check modal display toggles work", () => {
let wrapper;
let modal;
let updatedModal;
let openButton;
let openFunction;
test("<ManagePaymentSourceModal>", () => {
wrapper = setup();
modal = wrapper.find(`[data-test="amsp-manage-payment-source-modal"]`);
openButton = wrapper.find(
`[data-test="amsp-manage-payment-source-button"]`
);
openFunction = openButton.prop("onClick") as () => void;
expect(modal.prop("isOpen")).toBeFalsy();
openFunction();
updatedModal = wrapper.find(
`[data-test="amsp-manage-payment-source-modal"]`
);
expect(updatedModal.prop("isOpen")).toBeTruthy();
});
});
That last expect always fails saying it expected true and got false instead.
EDIT: I've tried tuning the test into an async function thing mabe it's the state change that just takes too long, but that didn't work either. This is how I've tried it:
test("<ManagePaymentSourceModal>", async () => {
wrapper = setup();
modal = wrapper.find(`[data-test="amsp-manage-payment-source-modal"]`);
openFunction = wrapper
.find(`[data-test="amsp-manage-payment-source-button"]`)
.prop("onClick") as () => void;
expect(modal.prop("isOpen")).toBe(false);
await openFunction();
updatedModal = wrapper.find(
`[data-test="amsp-manage-payment-source-modal"]`
);
expect(updatedModal.prop("isOpen")).toBe(true);
});
Changing my test to th following (invoke instead of prop) has solved my issue
openFunction = wrapper
.find(`[data-test="amsp-manage-payment-source-button"]`)
.invoke("onClick") as () => void;

Test onClick with Jest Mock Function

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,

Testing Semantic-UI Dropdown: How to wait for it to be populated?

I've successfully implemented the Semantic-UI Dropdown to display a list of companies. Now I'm trying to build Jest / React Testing Library tests for it. To accomplish this, I built this Mock Request:
beforeEach(() => {
request.get = jest.fn(() => Promise.resolve({
companies: [
{"company_id": 1, "company_name": "ABC"},
{"company_id": 2, "company_name": "DEF"}
]
}));
});
Based on a console.log I added to my component code, this appears to work as expected.
Here's an abridged example of my instance of this element:
<div id="companyId" data-testid="companies-dropdown" role="combobox">
<input autocomplete="companyId" class="search" type="text">
<div class="default text" "role="alert">Ex. ABC Corp</div>
<div role="listbox">
<div role="option"><span class="text">ABC</span></div>
<div role="option"><span class="text">DEF</span></div>
</div>
</div>
Where I'm struggling is to correctly wait in my test for the Dropdown to get populated:
it('Should populate the Search and Select Company dropdown with 2 companies', async () => {
const { getByTestId, getByText, getByRole } = displayModal();
const listBox = await waitForElement(() => getByRole('listbox'));
});
The error message is: Unable to find an accessible element with the role "listbox"
I also tried this alternate approach but got the same error message:
const listBox = await waitForElement(() => within(getByTestId('companies-dropdown')).getByRole('listbox'));
Might anyone have any ideas what I'm doing wrong?
I happened to see this while I was searching something else. But in your case, you are using wrong role. I achieved my use case with the following:
First focus the input element of the div to open dropdown. I use the input element:
userEvent.click(getByLabelText('Company'))
The above can be replaced by using data-testid or with role 'combobox'
Get all the roles for the select options:
getAllByRole('option')
const companyAbc = getAllByRole('option').find(ele => ele.textContent === 'ABC') userEvent.click(companyAbc); // verify your onChange event
In your async case:
const companyAbc = await findAllByRole('option').find(ele => ele.textContent === 'ABC') userEvent.click(companyAbc); // verify your onChange event
SemanticReact also has onSearchChange event that you can use to trigger for every input search if that's the use case.

Resources