I have such function:
export const clearInput = (ref: RefObject<HTMLInputElement>) => {
if (null !== ref.current) {
ref.current.value = ''
}
};
I completely don't know how to test it in react-testing-library / jest.
This is my current code:
import React, { RefObject, useRef } from 'react'
import { clearInput, isInputTextMatch } from '../input';
import { fireEvent, render, waitForElement, } from '#testing-library/react'
import { debug } from 'console';
const Component = () => {
const inputRef = useRef<HTMLInputElement>(null);
return (
<input ref={inputRef} data-testid="Input" value="example" />
)
}
describe('clearInput helper', () => {
test('If used function, input value should be clear', async () => {
const { findByTestId } = render(<Component />);
const inputNode = await waitForElement(() =>
findByTestId('Input')
)
fireEvent.change(inputNode, {target: { value: ""}})
});
})
Related
Child Component using WebSocket to get data from server
this working fine,
output of the code is correct when passing string
import { useContext, useEffect, useState } from "react";
import { WebsocketContext } from "./Wsconnection";
import React from "react";
const Allotments = ({ num }) => {
const [isReady, socket] = useContext(WebsocketContext);
const [allotdata, setAllotdata] = useState({});
useEffect(() => {
if (isReady) {
socket.send(JSON.stringify({ type: "get_allotment_details", number: num }));
socket.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === "get_allotment_details") {
const altdata = data.alloted_details[0];
setAllotdata({ customer: altdata.prq__customer__name });
}
};
}
return () => {};
}, [isReady, num, socket]);
return (
<div>
<h5>{allotdata?.customer}</h5>
</div>
);
};
export default Allotments;
Parent Component
import React from "react";
import Allotments from "./Allotments";
const init_alloted = JSON.parse(document.getElementById("reporting").dataset.alloted) || [];
export const Alloted = () => {
const allots = init_alloted.map((allot, idx) => {
return (
<div key={idx}>
<Allotments num={allot.number} key={idx} />
</div>
);
});
return <div className="col">{allots}</div>;
};
i tried passing text string then, everything works fine
I have a custom hook which includes a reference. How do I properly test such a hook and how do I mock the useRef()?
const useCustomHook = (
ref: () => React.RefObject<Iref>
): {
initializedRef: boolean
} => {
const [initializedRef, setInitializedRef] = useState<boolean>(false)
useEffect(() => {
if (ref) {
const { current } = ref()
// here comes your ref code
console.log({ refCurrent: current })
}
setInitializedRef(true)
}, [ref])
return { initializedRef }
}
Here are two possibilities:
view solution
import React, { useEffect, useRef, useState } from 'react'
import { renderHook, RenderHookResult, waitFor } from '#testing-library/react'
interface Iref {
current: any
}
const useCustomHookView = (
view: RenderHookResult<React.RefObject<Iref>, unknown>
): {
initializedView: boolean
} => {
const [initializedView, setInitializedView] = useState<boolean>(false)
useEffect(() => {
if (view && view.result) {
console.log({ viewResult: view.result.current })
}
setInitializedView(true)
}, [view])
return { initializedView }
}
describe('useRefMock', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('view', async () => {
const view = renderHook(() =>
useRef<Iref>({ current: 'my fake' })
)
const { result, rerender } = renderHook(() => useCustomHookView(view))
rerender()
expect(result.current.initializedView).toBeTruthy()
})
})
ref solution
import React, { useEffect, useRef, useState } from 'react'
import { renderHook, RenderHookResult, waitFor } from '#testing-library/react'
interface Iref {
current: any
}
const useCustomHookRef = (
ref: () => React.RefObject<Iref>
): {
initializedRef: boolean
} => {
const [initializedRef, setInitializedRef] = useState<boolean>(false)
useEffect(() => {
if (ref) {
const { current } = ref()
console.log({ refCurrent: current })
}
setInitializedRef(true)
}, [ref])
return { initializedRef }
}
describe('useRefMock', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('ref', async () => {
const ref = (): React.RefObject<Iref> => {
const { result } = renderHook(() => useRef<Iref>({ current: 'my fake' }))
console.log({ resultCurrent: result.current })
return result
}
const { result, rerender } = renderHook(() => useCustomHookRef(ref))
rerender()
expect(result.current.initializedRef).toBeTruthy()
})
I'm having the following problem with my react + nextJS project...
The component is something like this:
import React, { FC, useCallback, useEffect, useState } from 'react';
import InputMask, { Props } from 'react-input-mask';
import {
getPayersDetails,
PayerCompany,
PayerContact,
PayerDocuments,
} from 'services';
import { Formik } from 'formik';
import { Field, Loading, Page, Tooltip } from 'components';
import { Button, IconButton, Typography } from '#mui/material';
import { TextField, TextFieldProps } from '#material-ui/core';
import { SvgSelfCheckout } from 'images';
import {
FaEdit,
FaFileInvoiceDollar,
FaUserCheck,
FaUserAltSlash,
} from 'react-icons/fa';
import theme from 'styles/theme';
import * as S from './styles';
import { useRouter } from 'next/router';
import { PAYER_HOME } from 'src/routes';
const PayersDetails: FC = () => {
const [payerCompany, setPayerCompany] = useState<PayerCompany[]>([]);
const [payerContact, setPayerContact] = useState<PayerContact[]>([]);
const [payerDocument, setPayerDocument] = useState<PayerDocuments[]>([]);
const [loading, setLoading] = useState(true);
const [isActivePayer, setIsActivePayer] = useState(false);
const router = useRouter();
const getPayerDetails = useCallback(async (payerId: number) => {
setLoading(true);
const payerDetails = await getPayersDetails(payerId);
setPayerCompany(payerDetails.payerCompany);
setPayerDocument(payerDetails.payerDocument);
setPayerContact(payerDetails.payerContact);
setLoading(false);
}, []);
useEffect(() => {
if (!router.isReady) {
return;
}
const payerId = router.query.payerId as string;
try {
const safePayerId = parseInt(payerId);
getPayerDetails(safePayerId);
} catch (e) {
router.push(PAYER_HOME);
return;
}
}, [getPayerDetails, router]);
const contact = payerContact.length > 0 ? payerContact[0] : null;
const mobilePhone = payerContact
.filter(contact => contact.contactType.contactTypeName === 'mobile')
.map(contact => contact.value)[0];
return (
<Page
title="Detalhes do pagador"
pageTitle="Detalhes do pagador"
pageSubtitle="Dados pessoais"
pageSubitleColor={`${theme.palette.primary.light}`}
>
{loading ? (
<Loading show={loading} />
) : (
<Formik
enableReinitialize={true}
initialValues={{
name: contact?.contactName,
email: contact?.value,
}}
onSubmit={() => console.log('onSumit')}
>
....
)}
</Formik>
)}
</Page>
);
};
export default PayersDetails;
And I'm trying to test it with the following code:
import { render, screen, fireEvent } from '#testing-library/react';
import { getCompany } from 'services/companies';
import { getPayersDetails } from 'services/payers';
import PayersImport from '.';
import { useRouter } from 'next/router';
import userEvent from '#testing-library/user-event';
jest.mock('services/payers', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
getPayersDetails: jest.fn(),
}));
jest.mock('next/router', () => ({
useRouter: jest.fn().mockImplementation(() => ({
route: '/',
pathname: '',
query: '',
asPath: '',
})),
}));
describe('payers details layout', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
describe('when rendering', () => {
let getPayersDetailsMock;
beforeEach(() => {
getPayersDetailsMock = {
...
};
getPayersDetails.mockResolvedValue(getPayersDetailsMock);
useRouter.mockImplementation(() => ({
route: '/',
pathname: '',
isReady: true,
query: { payerId: 1 },
asPath: '',
}));
render(<PayersImport />);
});
it('Calls details api with the correct id', () => {
expect(getPayersDetails).toHaveBeenCalledWith(1);
});
});
});
The issue is:
The component load ok when we go to a browser, but when I run it on jest I get the following error:
console.error
Warning: An update to PayersDetails inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at PayersDetails (/home/thiago/finnet/repos/welcome/apps/lunapay-front/src/layouts/Payers/PayersDetails/index.tsx:29:43)
43 | setPayerContact(payerDetails.payerContact);
44 |
> 45 | setLoading(false);
| ^
46 | }, []);
47 |
48 | useEffect(() => {
I get it is only a warning and it wouldn't be a problem for me, but the issue is that it goes into a infinite loop trying to render again.
What am I doing wrong??
I solved the issue, but it was not the best solution.
Basically the render was being triggered by the <Loading /> component being rendered again.
What I did was just adding a new property telling me if it was already fetched and canceling the loop.
const [payerDetailsResponse, setPayerDetailsResponse] =
useState<PayerDetailsResponse | null>(null);
const [payerCompany, setPayerCompany] = useState<PayerCompany[]>([]);
const [payerContact, setPayerContact] = useState<PayerContact[]>([]);
const [payerDocument, setPayerDocument] = useState<PayerDocuments[]>([]);
const [loading, setLoading] = useState(true);
const [isActivePayer, setIsActivePayer] = useState(false);
const router = useRouter();
const getPayerDetails = useCallback(async (payerId: number) => {
setLoading(true);
const payerDetails = await getPayersDetails(payerId);
setPayerDetailsResponse(payerDetails);
setPayerCompany(payerDetails.payerCompany);
setPayerDocument(payerDetails.payerDocument);
setPayerContact(payerDetails.payerContact);
setLoading(false);
}, []);
useEffect(() => {
if (payerDetailsResponse) {
return;
}
if (!router.isReady) {
return;
}
const payerId = router.query.payerId as string;
try {
const safePayerId = parseInt(payerId);
getPayerDetails(safePayerId);
} catch (e) {
router.push(PAYER_HOME);
return;
}
}, [router, getPayerDetails]);
Sorry not to be able to help more, but that was a solution for my problem :)
Sometimes jest fall into an infinite loop with useEffect, if that is your case, you can make a mock of it
import React from 'react'
const mockUseEffect = jest.fn()
jest.spyOn(React, 'useEffect').mockImplementation(mockUseEffect)
and try with that and/or adapt to your needs
I have a custom hook called useScript:
import { useEffect } from 'react';
const useScript = scriptUrl => {
useEffect(() => {
const script = document.createElement('script');
script.src = scriptUrl;
script.async = true;
document.body.appendChild(script);
return () => document.body.removeChild(script);
}, [scriptUrl]);
};
export default useScript;
And I want to test it. I'm traying this way:
import React from "react";
import { renderHook } from "#testing-library/react-hooks";
import useScript from ".";
describe("useScript tests", () => {
it('verify that the script tag is created', () => {
const wrapper = ({children}) => <body>{children}</body>;
const initialProps = {
scriptUrl: 'https://crm.zoho.com/crm/javascript/zcga.js'
};
const { result } = renderHook(
() => useScript('https://crm.zoho.com/crm/javascript/zcga.js'),
{
initialProps,
wrapper
},
);
});
});
I don't know if I'm going the right way
This way:
import React from "react";
import useScript from ".";
import { render, } from '#testing-library/react';
describe("useScript tests", () => {
it('verify that the script tag is created', () => {
const scriptUrl = 'https://crm.zoho.com/crm/javascript/zcga.js';
const WrapperComponent = () => {
useScript(scriptUrl);
return null;
};
render(<WrapperComponent /> );
const script = document.body.querySelector(`script[src="${scriptUrl}"]`);
expect(script.src).toBe(scriptUrl);
});
});
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');
})