hello I'm writing test for a component with ref. I'd like to mock the ref element and change some properties but have no idea how to. Any suggestions?
import { test, expect } from "#playwright/experimental-ct-react";
import { useState } from "react";
import { useRef } from "react";
import Main from "./index";
test.use({ viewport: { width: 500, height: 500 } });
test("should work", async ({ mount }) => {
const refMain = useRef();
const refFooter = useRef();
const refMenuButton = useRef();
const [mainPostion, setMainPostion] = useState(startMainPostion);
const refContainerForMainFooter = useRef({
refMain,
refFooter,
refMenuButton,
});
const component = await mount(
<Main setMainPostion={setMainPostion} theme='light' ref={ref} />
);
// await expect(component).toContainText('DAMAN');
});
Related
I am new to tests in react and I don't know how to properly mock the useState value to properly cover the lines that uses the boolean as a parameter to return the component
React Code
import React from "react";
import { InputGroup, Input, Button, Spinner, Center } from "#chakra-ui/react";
import movieAPI from "../../services/movieAPI";
import { useNavigate } from "react-router-dom";
import styles from "./index.module.css";
export const Search = () => {
const navigate = useNavigate();
const [movie, setMovie] = React.useState("");
const [isLoading, setLoading] = React.useState(false);
const handleClick = async () => {
setLoading(true);
const data = await movieAPI.fetchMovieByTitle(movie);
setLoading(false);
navigate(`/movie/${data.Title}`, { state: data });
};
return isLoading ? (
<Center>
<Spinner
data-testid="spinner"
className={styles.verticalCenter}
thickness="6px"
speed="1.0s"
emptyColor="gray.200"
color="green.500"
size="xl"
/>
</Center>
) : (
<InputGroup m={2} p={2}>
<Input onChange={(e) => setMovie(e.target.value)} placeholder="Search" />
<Button onClick={handleClick}>Search</Button>
</InputGroup>
);
};
How can I mock the property loading in order to cover the specific line of the spinner component?
down below is my attempt to test the Spinner code
import { render, fireEvent, RenderResult } from "#testing-library/react";
import { Search } from "../../components/search/search";
import { BrowserRouter as Router, useNavigate } from "react-router-dom";
import React from "react";
describe("search.tsx", () => {
let isLoading = false;
let setLoading = jest.fn();
let container: RenderResult<
typeof import("#testing-library/dom/types/queries"),
HTMLElement,
HTMLElement
>;
jest.mock("react", () => {
return {
...jest.requireActual("react"),
useState: () => ({
isLoading: isLoading,
setLoading: setLoading,
}),
};
});
beforeEach(() => {
container = render(
<Router>
<Search />
</Router>
);
});
it("should render spinner", async () => {
setLoading.mockImplementation((data) => {
isLoading = data;
});
setLoading(true);
console.log(await container.findByTestId("spinner"));
});
});
A component is like a black box for testing.
It has two inputs: props and user interaction. Based on those it renders something. you should not mock useState. Your test would look like this:
You can mock other dependencies like localStorage and or rest api calls. But no internal component implementation.
Your test should look like this, written in pseudo code
it("Should show loader while searching for movies", () => {
// mock the API to return promise which never resolves
// render component
// input some search data
// click the search button
// expect the loader to be visible
});
it("Should reflect text base on user input,", () => {
// render component
// input some search data "Start"
// expect searchInput.text to have value = "Start"
})
I am writing tests for components of my projects and one of components is changing context value. How can I check if click actually changes context value? Code I have right now gives error "Invalid hook call. Hooks can only be called inside of the body of a function component." What is the way to actually use hooks in tests?
import { useState, useContext } from "react";
import { IntlProvider } from "react-intl";
import { BrowserRouter } from "react-router-dom";
import { render, screen, fireEvent } from "#testing-library/react";
import { messages } from "../../App/App.const";
import Navbar from "./Navbar";
import { DarkModeContext } from "../../contexts/DarkModeContext";
function NavbarMock() {
const [search, setSearch] = useState("");
const [language, setLanguage] = useState("en");
return (
<IntlProvider
messages={messages[language as keyof typeof messages]}
locale={language}
defaultLocale="en"
>
<BrowserRouter>
<Navbar
setLanguage={setLanguage}
language={language}
setSearch={setSearch}
search={search}
/>
</BrowserRouter>
</IntlProvider>
);
}
describe("testing navbar component", () => {
test("renders logo correctly", async () => {
render(<NavbarMock />);
const logo = screen.getByText(/Todoly/i);
expect(logo).toBeInTheDocument();
});
test("renders mode icon correctly", async () => {
render(<NavbarMock />);
const svgEl = screen.getByTitle("mode icon");
expect(svgEl).toBeInTheDocument();
});
test("mode changes", async () => {
render(<NavbarMock />);
const svgEl = screen.getByTitle("mode icon");
const { isDarkMode } = useContext(DarkModeContext);
fireEvent.click(svgEl);
expect(isDarkMode).toBe(true);
});
test("renders language icon correctly", async () => {
render(<NavbarMock />);
const flagEl = screen.getByAltText("en");
expect(flagEl).toBeInTheDocument();
});
});
Ok, the error isn't caused by any of the provider, instead it's caused by the way you write useContext.
A hook can't be used unless it's referenced inside a Component, this is the RULE by React hooks. Because states defined by hooks are meaningless outside.
const AnotherNavbarMock = () => {
const { isDarkMode } = useContext(DarkModeContext);
return <NavbarMock />
}
The above code is the working example. Basically a test isn't a component, you have to define a component and then use it via <AnotherNavbarMock />, otherwise all the hook code would fail.
While I'm trying to make a test
import React from "react";
import {EventsLoadingContext} from "../../../src/components/data-admin/context/EventsLoadingContext";
import DataAdmin from "../../../src/components/data-admin/DataAdmin";
import { configure, mount, shallow } from "enzyme";
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
it("should render DataAdmin component", () => {
const component = shallow(<DataAdmin />, {context: EventsLoadingContext});
const dataTable = component.find(".table-container");
expect(dataTable.length).toBe(1);
});
and have a trouble like that:
In the context component I have:
export const EventsLoadingContext = createContext();
export const WithEventsLoadingContext = ({children}) => {
const [backendEvents, setBackendEvents] = useState([]);
const [integrationEvents, setIntegrationEvents] = useState([]);
const [allEvents, setAllEvents] = useState([]);
// ...
const value = {
preparedEvents,
dropdownItems,
dropdownClicked, setDropdownClicked,
loading,
updatedChanges, setUpdatedChanges
};
return (
<EventsLoadingContext.Provider value = {value}>
{children}
</EventsLoadingContext.Provider>
)
};
And in the DataAdmin component I have:
const DataAdmin = () => {
const {
preparedEvents,
dropdownItems,
dropdownClicked, setDropdownClicked,
loading,
updatedChanges, setUpdatedChanges
} = useContext(EventsLoadingContext);
and so on.
So how could I fix that?
So how could I fix that?
Seems like you should pass the EventsLoadingContext context in options parameter for shallow method. See shallow documentation.
I'm writing a test code with Jest for a custom hook in my web application.
It uses Recoil for state management, but the error message appears when I run npm run test.
This is the error message.
This component must be used inside a <RecoilRoot> component.
16 | const useIds = () => {
17 | // const [ids, setIds] = React.useState([]);
> 18 | const [ids, setIds] = useRecoilState(idsState);
| ^
This is the test code.
import * as React from 'react';
import { render, fireEvent } from '#testing-library/react';
import { useIds } from '#/hooks/useIds';
import { RecoilRoot } from 'recoil';
it('unit test for custom hook useIds', () => {
const TestComponent: React.FC = () => {
const ids = useIds();
return (
<RecoilRoot>
<div title='ids'>{ ids }</div>
</RecoilRoot>
)
}
const { getByTitle } = render(<TestComponent />);
const ids = getByTitle('ids');
})
This is the custom hook code
import * as React from 'react';
import { useRouter } from 'next/router';
import { atom, useRecoilState } from 'recoil';
import { fetchIdsByType } from '#/repositories';
const initialState: {
[type: string]: number[];
} = {};
export const idsState = atom({
key: 'idsState',
default: initialState,
});
const useIds = () => {
const [ids, setIds] = useRecoilState(idsState);
const router = useRouter();
const { type } = router.query;
React.useEffect(() => {
if (router.asPath !== router.route) {
// #ts-ignore
fetchIdsByType(type).then((ids: number[]) => {
setIds((prevState) => {
return {
...prevState,
// #ts-ignore
[type]: ids,
};
});
});
}
}, [router]);
// #ts-ignore
return ids[type];
};
export { useIds };
I know why the error is happening but I have no idea where the RecoilRoot should be in?
You might need to put where to wrap the component which is using your custom hook as following:
it('unit test for custom hook useIds', () => {
const TestComponent: React.FC = () => {
const ids = useIds();
return (
<div title='ids'>{ ids }</div>
)
}
const { getByTitle } = render(
// Put it here to wrap your custom hook
<RecoilRoot>
<TestComponent />
</RecoilRoot>
);
const ids = getByTitle('ids');
})
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.