How to test react hooks
I have a fairly complicated hooks here
import IProduct, { productSchema } from "../../prisma/types/IProduct";
import { useState } from "react";
interface ResponseType {
message: string;
data: Record<string, string>;
}
const useProductsHandler = (defaultProducts: IProduct[] = []) => {
const [products, setProducts] = useState<IProduct[]>(defaultProducts);
const initFromCSV = (data: string[][]): string[] => {
const errors: string[] = [];
const newProducts = data
.filter((row: string[]) => {
const exist = products.find(
(product) => product.sku.trim() === row[1].trim()
);
if (exist) errors.push(exist.sku);
return !exist;
})
.map((row: string[]) => {
const product: IProduct = {
quantity: Number(row[0].trim()),
sku: row[1].trim(),
oldSku: row[1].trim(),
description: row[2].trim(),
store: row[3].trim(),
isSaved: false,
};
productSchema.validate(product, { abortEarly: false }).catch((err) => {
product.errors = err.errors;
});
return product;
});
setProducts([...products, ...newProducts]);
return errors;
};
const initFromAPI = (data: IProduct[]): void => {
const newProducts = data.map((product) => {
product.isSaved = true;
return product;
});
setProducts(newProducts);
};
const ifSkusExists = (sku: string): boolean => {
const exist: IProduct | undefined = products.find(
(product) => product.sku.trim() === sku.trim()
);
return !!exist;
};
const update = (sku: string, data: IProduct): void => {
const newProducts = products.map((product: IProduct) => {
if (product.sku === sku) {
data.isSaved = false;
data.oldSku = product.sku;
return data;
}
return product;
});
setProducts(newProducts);
};
const remove = (sku: string): void => {
const newProducts = products.filter((product: IProduct) => {
return product.sku !== sku;
});
setProducts(newProducts);
};
const clear = (): void => {
setProducts([]);
};
const saveAll = (response: ResponseType): void => {
const { data } = response;
products.forEach((product) => {
if (data[product.sku] === "created" || data[product.sku] === "updated") {
product.isSaved = true;
} else if (data[product.sku] === "error") {
product.isSaved = false;
product.errors = [data[product.sku]];
}
});
};
const getToSaveProducts = (): IProduct[] => {
const newProducts: IProduct[] = [];
products.forEach((product) => {
if (!product.isSaved) {
newProducts.push({
sku: product.sku,
oldSku: product.oldSku,
description: product.description,
quantity: product.quantity,
store: product.store,
});
}
});
return newProducts;
};
const add = (data: IProduct) => {
let error = null;
const exist = products.find(
(product) => product.sku.trim() === data.sku.trim()
);
if (exist) {
error = "SKU already exists";
return error;
}
data.isSaved = false;
data.oldSku = data.sku;
setProducts([...products, data]);
return error;
};
return {
products,
initFromCSV,
initFromAPI,
update,
remove,
clear,
add,
ifSkusExists,
saveAll,
getToSaveProducts,
};
};
Make sure you have jest installed, and edit your tsconfig.ts
"types": ["@remix-run/node", "vite/client", "jest"],
then you need ts-jest and babel-jest with your jest.config.ts
module.exports = {
preset: "ts-jest",
testEnvironment: "jsdom",
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
verbose: true,
};
And in order to test it, I needed to first install `@testing-library/react` and then Mock my prisma layer
import { act } from "react-dom/test-utils";
import useProductsHandler from "../useProductsHandler";
import { renderHook } from "@testing-library/react";
jest.mock("../../../prisma/types/IProduct", () => ({
productSchema: {
validate: jest.fn().mockReturnValue({
catch: jest.fn().mockResolvedValue({ errors: [] }),
}),
},
}));
export default useProductsHandler;
Then the first test could be as easy as:
test("initFromCSV to be working", async () => {
const { result } = renderHook(() => useProductsHandler());
const { initFromCSV } = result.current;
act(() => {
const errors = initFromCSV([["1", "SK-123", "desc", "store"]]);
expect(errors).toStrictEqual([]);
});
});
and the rest would be similar
test("initFromAPI returns errors", async () => {
const { result } = renderHook(() =>
useProductsHandler([
{
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: false,
},
])
);
const { initFromCSV } = result.current;
act(() => {
const errors = initFromCSV([["1", "123", "desc", "store"]]);
expect(errors).toStrictEqual(["123"]);
});
});
test("initFromAPI to be working", async () => {
const { result } = renderHook(() => useProductsHandler());
const { initFromAPI } = result.current;
act(() => {
initFromAPI([
{
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: false,
},
]);
});
expect(result.current.products).toStrictEqual([
{
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: true,
},
]);
});
test("ifSkusExists to be working", async () => {
const { result } = renderHook(() =>
useProductsHandler([
{
quantity: 1,
sku: "SK-123",
oldSku: "SK-123",
description: "desc",
store: "store",
isSaved: false,
},
])
);
const { ifSkusExists } = result.current;
act(() => {
const exists = ifSkusExists("SK-123");
expect(exists).toBe(true);
});
});
test("update to be working", async () => {
const { result } = renderHook(() =>
useProductsHandler([
{
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: false,
},
])
);
const { update } = result.current;
act(() => {
update("123", {
quantity: 2,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: false,
});
});
expect(result.current.products).toStrictEqual([
{
quantity: 2,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: false,
},
]);
});
test("getToSaveProducts to be working", async () => {
const { result } = renderHook(() =>
useProductsHandler([
{
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: false,
},
])
);
const { getToSaveProducts } = result.current;
act(() => {
const toSave = getToSaveProducts();
expect(toSave).toStrictEqual([
{
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
},
]);
});
});
test("getToSaveProducts to be working but return nothing", async () => {
const { result } = renderHook(() =>
useProductsHandler([
{
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: true,
},
])
);
const { getToSaveProducts } = result.current;
act(() => {
const toSave = getToSaveProducts();
expect(toSave).toStrictEqual([]);
});
});
test("add to be working", async () => {
const { result } = renderHook(() => useProductsHandler());
const { add } = result.current;
act(() => {
add({
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: false,
});
});
expect(result.current.products).toStrictEqual([
{
quantity: 1,
sku: "123",
oldSku: "123",
description: "desc",
store: "store",
isSaved: false,
},
]);
});
test("add to be working but return error", async () => {
const { result } = renderHook(() =>
useProductsHandler([
{
quantity: 1,
sku: "SK-123",
oldSku: "SK-123",
description: "desc",
store: "store",
isSaved: false,
},
])
);
const { add } = result.current;
act(() => {
const error = add({
quantity: 1,
sku: "SK-123",
oldSku: "SK-123",
description: "desc",
store: "store",
isSaved: false,
});
expect(error).toBe("SKU already exists");
});
});