I have 2 simple components. I am writing tests for them using jest. I want to test when I click on currencyItem, the respective array should be added to the select state. As you can see I am passing handleCurrencyToggle
function as props to the currencyItem hence I want to mock it in jest test too.
CurrencySelector.tsx
import { useEffect, useState } from "react";
import { currencies } from "../constants";
import { Currency } from "../types";
import CurrencyItem from "./CurrencyItem";
const CurrencySelector = () => {
const initialSelected = () => {
const storedCurrencies = sessionStorage.getItem("currencies");
return storedCurrencies ? JSON.parse(storedCurrencies) : [];
};
const [selected, setSelected] = useState<Currency[]>(initialSelected);
const handleCurrencyToggle = (currency: Currency) => {
const isSelected = selected.some(
(selectedCurrency) => selectedCurrency.name === currency.name
);
if (isSelected) {
setSelected((prevSelected) =>
prevSelected.filter(
(selectedCurrency) => selectedCurrency.name !== currency.name
)
);
} else {
setSelected((prevSelected) => [...prevSelected, currency]);
}
};
useEffect(() => {
sessionStorage.setItem("currencies", JSON.stringify(selected));
}, [selected]);
return (
<div className="currency-selector">
<div className="selected-currencies currency-items">
{selected.length > 0 ? (
selected.map((currency: Currency, index: number) => (
<div
data-testid="selected-currency"
key={index}
className="selected-currency currency"
>
<span>{currency.name}</span>
<button
className="unselect-currency"
onClick={() => handleCurrencyToggle(currency)}
>
x
</button>
</div>
))
) : (
<div data-testid="no-selected-currencies" className="no-currencies">
No selected currencies
</div>
)}
</div>
<div className="currency-items">
{currencies.map((currency: Currency, index: number) => (
<CurrencyItem
data-testid={`currency-item-${index}`}
key={index}
currency={currency}
onClick={() => handleCurrencyToggle(currency)}
isSelected={selected.some(
(selectedCurrency) => selectedCurrency.name === currency.name
)}
/>
))}
</div>
</div>
);
};
export default CurrencySelector;
CurrencyItem.tsx
import { Currency } from "../types";
const CurrencyItem = ({
currency,
onClick,
isSelected,
}: {
currency: Currency;
onClick: () => void;
isSelected: boolean;
}) => {
const handleCheckboxChange = () => {
onClick();
};
return (
<div data-testid="currency-item" onClick={onClick} className="currency">
<label>
<input onChange={handleCheckboxChange} checked={isSelected} type="checkbox" />
<span className="checkbox-container"></span>
</label>
<span>{currency.name}</span>
</div>
);
};
export default CurrencyItem;
currency array:
import { Currency } from "../types";
export const currencies: Currency[] = [
{
name: "EUR",
},
{
name: "PLN",
},
{
name: "GEL",
},
{
name: "DKK",
},
{
name: "CZK",
},
{
name: "GBP",
},
{
name: "SEK",
},
{
name: "USD",
},
{
name: "RUB",
},
];
my test:
it("adds currency to select list", () => {
const handleCurrencyToggle = jest.fn()
const { getByTestId: getByTestId2 } = render(
<CurrencySelector />
);
const { getByTestId } = render(
<CurrencyItem
key={1}
currency={currencies[0]}
onClick={() => () => {
handleCurrencyToggle();
}}
isSelected={true}
/>
);
const currencyItem = getByTestId("currency-item");
fireEvent.click(currencyItem);
const selectItem = getByTestId2("selected-currency").textContent
expect(selectItem).toBe("EUR");
});
});
I know it’s not correct but please guide me how can I mock the real handleCurrencyToggle function.
3
Answers
I eventually solved this.
To properly test the integration between CurrencySelector and CurrencyItem components, you can mock the handleCurrencyToggle function in the CurrencySelector component and ensure that it is called with the correct parameters when a CurrencyItem is clicked.
the handleCurrencyToggle function is mocked and replaced with mockedHandleCurrencyToggle in the CurrencySelector component. Then, in your test for CurrencyItem, you can check if mockedHandleCurrencyToggle is called with the expected parameters.
The above code assumes that the CurrencySelector component imports handleCurrencyToggle from the same file. If it’s imported from a different file, you should adjust the jest.mock path accordingly.
To test something, you need first to assign responsibilities. Separate objects apart and test them separately.
Based on your example, your
CurrencySelector
component has too many responsibilities. It accesses the local storage, it has the knowledge where and how the selected currencies are stored. You need to take that away.The best way to do that is to have some custom hooks. This way, you can mock things separately and test them in isolation.
You may need useLocalStorage hook which handles all local storage manipulations.
Some code here:
After that all logic for setting and getting selected currencies may be isolate into its own hook like this:
Now, the component has dependency ONLY on the custom hook
And we need to test only the hook for the toggling logic:
Link to a working example here