I have created a custom hook called useCity. It is wrapping an API call made using useSWR.
Here is the code for hook:
import useSWR from 'swr';
import { City } from '../interfaces';
import { BASE_URL } from '../../config';
interface CitiesResponse {
data?: {
records: {
fields: {
city: string;
accentcity: string;
}
}[]
},
error?: {
message: string;
}
};
interface Props {
start?: number;
rows: number;
query?: string;
sort?: 'population';
exclude?: string[];
}
const useCity = ({ start = 0, rows, query, sort, exclude }: Props) => {
const params = [`start=${start}`, `rows=${rows}`];
if (query) params.push(`q=${query}`);
if (sort) params.push(`sort=${sort}`);
if (exclude && exclude.length > 0) params.push(...exclude.map(city => `exclude.city=${city}`))
const { data, error }: CitiesResponse = useSWR(
`${BASE_URL.CITIES_SERVICE}?dataset=worldcitiespop&facet=city&${params.join('&')}`,
{ revalidateOnFocus: false, }
);
const cities: City[] = data?.records.map(record => ({
name: record.fields.city,
title: record.fields.accentcity,
})) || [];
return {
cities,
loading: !error && !data,
error,
};
};
export default useCity;
Now, I need to test the hook. So, I tried using msw
and @testing-library/react-hooks
.
Here is my try:
const server = setupServer(
rest.get(BASE_URL.CITIES_SERVICE, (req, res, ctx) => {
const start = req.url.searchParams.get('start');
const rows = req.url.searchParams.get('rows');
const query = req.url.searchParams.get('query');
const sort = req.url.searchParams.get('sort');
const exclude = req.url.searchParams.getAll('exclude.city');
const getReturnVal: () => DatabaseCity[] = () => {
// i will write some code that assumes what server will return
};
return res(
ctx.status(200),
ctx.json({
records: getReturnVal(),
}),
);
}),
...fallbackHandlers,
);
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
cache.clear();
});
afterAll(() => server.close());
it('should return number of cities equal to passed in rows', async () => {
const wrapper = ({ children } : { children: ReactNode }) => (
<SWRConfig value={{ dedupingInterval: 0 }}>
{children}
</SWRConfig>
);
const { result, waitForNextUpdate, } = renderHook(() => useCity({ rows: 2 }), { wrapper });
const { cities:_cities, loading:_loading, error:_error } = result.current;
expect(_cities).toHaveLength(0);
await waitForNextUpdate();
const { cities, loading, error } = result.current;
expect(cities).toHaveLength(2);
});
I assme that the test case will pass once I implement the mock function.
But I don't know if this is the right approach to test such a hook. I am a frontend developer, is this my responsibility to test that API call?
I am new to writing test cases that involves API calls. Am I going in the right direction? I don't know what this kind of tests are called. If someone can tell me the kind of the test I am perfoming, then it will help me google for the solutions instead of wasting other developer's time to answer my questions.
Looks like you are on the right track.
Your
useCity
hook does basically 2 things that you can validate in tests:You can validate
useSWR
is called with the correct url by using a spy:You can validate
useCities
returns correct cities byI think that is up to you to find the answer. I personally see as my responsibility to test any code that I write--that of course is not a dogma and is context sensitive.
There might not be a clear answer for this. Some people would call it unit testing (since
useCities
is a "unit"). Others might call it integration testing (since you testuseCities
anduseSWR
in "integration").Your best bet would be to google things like "how to test react hooks" or "how to test react components". The RTL docs are a good place to start.
Extra notes
I personally almost never test hooks in isolation. I find it easier and more intuitive to write integration tests for the components that use the hooks.
However, if your hook is going to be used by other projects, I think it makes sense testing them in isolation, like you are doing here.