I'm struggling with a test for a custom react hook.
I have two custom hooks: the first one returns the size of my window:
import { useEffect, useState } from 'react';
export const useWindowSize = () => {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener('resize', handleResize);
handleResize();
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
};
The second one returns an object to check whether or not I should expand my sidebar
import { useMemo } from 'react';
import { SIDEBAR_BREAKPOINT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_EXPANDED } from 'config';
import { useWindowSize } from 'hooks';
export const useSidebarWidth = (): { sidebarWidth: string; isExpanded: boolean } => {
const { width } = useWindowSize();
const isExpanded = useMemo(() => {
if (width < SIDEBAR_BREAKPOINT) {
return false;
}
return true;
}, [width]);
return {
sidebarWidth: isExpanded ? SIDEBAR_WIDTH_EXPANDED : SIDEBAR_WIDTH,
isExpanded,
};
};
Now, when trying to test it, I'm stuck between a rock and a hard place:
import { renderHook } from '#testing-library/react-hooks';
import { useSidebarWidth } from 'hooks';
describe('useSidebarWidth', () => {
it('should return 72px as sidebarWidth when width is less than 1440px', () => {
const { result } = renderHook(() => useSidebarWidth());
expect(result.current.sidebarWidth).toBe('72px');
});
it('should return 248px as sidebarWidth when width is more than 1440px', () => {
const { result } = renderHook(() => useSidebarWidth());
expect(result.current.sidebarWidth).toBe('248px');
});
});
In this test, I have two issues: if I run it as is, obviously, the second test will fail (as I didn't change anything regarding my window size). So I figured I'd have to mock the result of my first hook. I added this atop of my test file:
let windowWidth = 700;
jest.mock('hooks', () => ({
useWindowSize: jest.fn(() => ({ width: windowWidth })),
}));
And added this in my second test:
windowWidth = 1500;
But, when using this, it raises the following error:
TypeError: (0 , _hooks.useSidebarWidth) is not a function
How can I get the best of both world and mock my window size to make sure that both test run smoothly ?
hello man i think your problem is with mocking the hooks file which contains both hooks and you only returned useWindowSize.
so instead u should mock useSidebarWidth too :
let windowWidth = 700;
jest.mock('hooks', () => ({
useWindowSize: jest.fn(() => ({ width: windowWidth })),
useSidebarWidth: jest.fn(() => ({sidebarWidth: "72px",isExpanded:true})),
}));
or you can use requireActual if you don't want to mock useSideBar like this :
let windowWidth = 700;
jest.mock('hooks', () => ({
__esModules: true,
...jest.requireActual('hooks')
useWindowSize: jest.fn(() => ({ width: windowWidth })),
}));
Related
We have a functionality, when the content is not visible on the screen then we scroll content till the end of content. here is the hooks i have written. quite new to testing Lib it would be great if someone have already written unit test for something like this.
import { useEffect, useRef } from 'react';
export default function useOnScreen() {
const ref = useRef<HTMLElement | null>();
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
const scrollToView = (timer: number) => {
const scrollRef = ref?.current;
if (ref) {
timeoutRef.current && clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
scrollRef?.current.scrollIntoView({
behavior: 'smooth',
block: 'end',
inline: 'end',
});
}, timer);
}
};
return [ref, scrollToView];
}
Need help here to write test case
import useOnScreen from '../useOnScreen';
import {renderHook, act} from '#testing-library/react-hooks';
describe('useOnScreen', () => {
it('useOnScreen', () => {
const [ref, scrollToView] = renderHook(() => useOnScreen());
});
});
I have this simple hook:
export const useOverflow = (): {
isOverflow: boolean;
elementRef: React.RefObject<HTMLParagraphElement>;
} => {
const elementRef = createRef<HTMLParagraphElement>();
const [isOverflow, setIsOverflow] = useState(false);
useLayoutEffect(() => {
if (elementRef.current) {
setIsOverflow(
elementRef.current.scrollWidth > elementRef.current.offsetWidth,
);
}
}, [elementRef]);
return { isOverflow, elementRef };
};
How do I test this with testing-library?
I have tried using a test component, but it falls into the issue that refs do not work with tests, so I would need to mock this ref somehow, but in my case it is not passed as a prop, but it is returned from the hook.
thanks in advance =)
Found the solution for anyone doing this for the first time:
Must spy on createRef method and mock the return value as needed.
const renderTest = () => renderHook(() => useOverflow());
describe('useOverflow', () => {
it('should set overflow to true', () => {
jest.spyOn(React, 'createRef').mockReturnValue({
current: {
scrollWidth: 5,
offsetWidth: 2,
},
});
const { result } = renderTest();
expect(result.current.isOverflow).toBe(true);
});
});
I am only starting with unit testing now and the course I am following has the following syntax for a test:
expect(app.state().gifts).toEqual([])
This is the syntax for the use of class components but that will be deprecated soon so I am using React function components instead.
How do you accomplish the same test with hooks?
Thanks
You can use react-hooks-testing-library and test your hooks as well. Basic Hooks
Example :
useDisclosure.ts
import * as React from 'react';
export const useDisclosure = (initial = false) => {
const [isOpen, setIsOpen] = React.useState(initial);
const open = React.useCallback(() => setIsOpen(true), []);
const close = React.useCallback(() => setIsOpen(false), []);
const toggle = React.useCallback(() => setIsOpen((state) => !state), []);
return { isOpen, open, close, toggle };
};
useDisclosure.test.ts
import { renderHook, act } from '#testing-library/react-hooks';
import { useDisclosure } from '../useDisclosure';
test('should open the state', () => {
const { result } = renderHook(() => useDisclosure());
expect(result.current.isOpen).toBe(false);
act(() => {
result.current.open();
});
expect(result.current.isOpen).toBe(true);
});
test('should close the state', () => {
const { result } = renderHook(() => useDisclosure());
expect(result.current.isOpen).toBe(false);
act(() => {
result.current.close();
});
expect(result.current.isOpen).toBe(false);
});
test('should toggle the state', () => {
const { result } = renderHook(() => useDisclosure());
expect(result.current.isOpen).toBe(false);
act(() => {
result.current.toggle();
});
expect(result.current.isOpen).toBe(true);
act(() => {
result.current.toggle();
});
expect(result.current.isOpen).toBe(false);
});
test('should define initial state', () => {
const { result } = renderHook(() => useDisclosure(true));
expect(result.current.isOpen).toBe(true);
act(() => {
result.current.toggle();
});
expect(result.current.isOpen).toBe(false);
});
Here is my custom hook:
export function useClientRect() {
const [scrollH, setScrollH] = useState(0);
const [clientH, setClientH] = useState(0);
const ref = useCallback(node => {
if (node !== null) {
setScrollH(node.scrollHeight);
setClientH(node.clientHeight);
}
}, []);
return [scrollH, clientH, ref];
}
}
I want each time that it is called, it return my values. like:
jest.mock('useClientRect', () => [300, 200, () => {}]);
How can I achieve this?
Load the hook as a module. Then mock the module:
jest.mock('module_name', () => ({
useClientRect: () => [300, 200, jest.fn()]
}));
mock should be called on top of the file outside test fn. Therefore we are going to have only one array as the mocked value.
If you want to mock the hook with different values in different tests:
import * as hooks from 'module_name';
it('a test', () => {
jest.spyOn(hooks, 'useClientRect').mockImplementation(() => ([100, 200, jest.fn()]));
//rest of the test
});
Adding on to this answer for typescript users encountering the TS2339: Property 'mockReturnValue' does not exist on type error message. There is now a jest.MockedFunction you can call to mock with Type defs (which is a port of the ts-jest/utils mocked function).
import useClientRect from './path/to/useClientRect';
jest.mock('./path/to/useClientRect');
const mockUseClientRect = useClientRect as jest.MockedFunction<typeof useClientRect>
describe("useClientRect", () => {
it("mocks the hook's return value", () => {
mockUseClientRect.mockReturnValue([300, 200, () => {}]);
// ... do stuff
});
it("mocks the hook's implementation", () => {
mockUseClientRect.mockImplementation(() => [300, 200, () => {}]);
// ... do stuff
});
});
Well, this is quite tricky and sometimes developers get confused by the library but once you get used to it, it becomes a piece of cake. I faced a similar issue a few hours back and I'm sharing my solution for you to derive your solution easily.
My custom Hook:
import { useEffect, useState } from "react";
import { getFileData } from "../../API/gistsAPIs";
export const useFilesData = (fileUrl: string) => {
const [fileData, setFileData] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
setLoading(true);
getFileData(fileUrl).then((fileContent) => {
setFileData(fileContent);
setLoading(false);
});
}, [fileUrl]);
return { fileData, loading };
};
My mock code:
Please include this mock in the test file outside of your test function.
Note: Be careful about the return object of mock, it should match with the expected response.
const mockResponse = {
fileData: "This is a mocked file",
loading: false,
};
jest.mock("../fileView", () => {
return {
useFilesData: () => {
return {
fileData: "This is a mocked file",
loading: false,
};
},
};
});
The complete test file would be:
import { render, screen, waitFor } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import FileViewer from "../FileViewer";
const mockResponse = {
fileData: "This is a mocked file",
loading: false,
};
jest.mock("../fileView", () => {
return {
useFilesData: () => {
return {
fileData: "This is a mocked file",
loading: false,
};
},
};
});
describe("File Viewer", () => {
it("display the file heading", async () => {
render(<FileViewer fileUrl="" filename="regex-tutorial.md" className="" />);
const paragraphEl = await screen.findByRole("fileHeadingDiplay");
expect(paragraphEl).toHaveTextContent("regex-tutorial.md");
});
}
I am trying to test a functional component in that functional component when I do the resize of the window I am calculating the height using useLayoutEffect.
The feature is working, but I was not able to find a proper doc for testing with hooks.
So what I have tried is
app.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "..";
describe("App Page", () => {
it("should render App Page", () => {
const wrapper = shallow(<App />);
expect(wrapper).toMatchSnapshot();
});
it("should adjust the height on window resize", () => {
const wrapper = shallow(<App />);
global.innerHeight = 600;
global.dispatchEvent(new Event("resize"));
console.log(wrapper.debug()); // how can i test the useLayoutEffect
});
});
app.js
import React, { useLayoutEffect, useState, useEffect, useRef } from "react";
import { Layout } from "antd";
const { Header } = Layout;
function useWindowSize() {
const isClient = typeof window === "object";
function getSize() {
return {
width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined,
};
}
const [windowSize, setWindowSize] = useState(getSize);
useEffect(() => {
if (!isClient) {
return false;
}
function handleResize() {
setWindowSize(getSize());
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount and unmount
return windowSize;
}
const App = () => {
const headerRef = useRef(null);
const size = useWindowSize();
const [barHeight, setBarHeight] = useState(56);
useLayoutEffect(() => {
setBarHeight(headerRef.current.offsetHeight);
}, [size]);
return (
<Layout className="layout">
<HeaderContainer ref={headerRef}>
<Header>.....</Header>
</HeaderContainer>
</Layout>
);
};
export default App;
Should I use https://www.npmjs.com/package/#testing-library/react-hooks for these or is there any way to test it using Enzyme itself.