React

useAxios 개별 테스트 코드 작성 필요 - renderHook? error handling

Asset Type
File Type
When to use
Last edited time
2022/05/05 12:57
Created by
Reference
import { renderHook } from '@testing-library/react-hooks'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import useAxios from './useAxios'; const stubbedStocks = [ { symbol: 'AAPL', price: 1 }, { symbol: 'GOOG', price: 2 }, ]; const stubbedFetchUrl = 'api/stocksUrl-mocked'; const server = setupServer(); beforeEach(() => server.resetHandlers()); beforeAll(() => server.listen()); afterAll(() => server.close()); describe('useAxios는', () => { beforeEach(() => { server.use( rest.get(stubbedFetchUrl, (req, res, ctx) => { return res(ctx.json(stubbedStocks)); }) ); }); it('페치 후에 데이터를 반환해야만 한다.', async () => { const { result, waitForNextUpdate } = renderHook(() => useAxios(stubbedFetchUrl) ); await waitForNextUpdate(); expect(result.current).toStrictEqual({ loading: false, data: stubbedStocks, error: null, }); }); it('에러를 캐치해야만 한다.', async () => { // jest.spyOn(global, 'fetch').mockImplementation(() => // Promise.resolve({ // json: () => Promise.reject('oops, error occured!'), // }) // ); const { result, waitForNextUpdate } = renderHook(() => useAxios(stubbedFetchUrl) ); await waitForNextUpdate(); expect(result.current).toStrictEqual({ loading: false, data: [], error: 'oops, error occured!', }); }); });
TypeScript
복사
hook 테스트를 위해 별도의 컴포넌트를 작성하여 hook을 호출할 필요가 없다. 내부적으로 HTTP request를 사용하는 hook이나, 복잡한 컴포넌트 로직 내에서 동작하는 hook의 경우에 react-hooks-testing-library 를 사용하여 간단하게 테스트를 진행해볼 수 있다.
renderHook에 테스트하고자 하는 hook 함수를 넘겨주고, 반환값인 result를 이용하여 hook 호출의 성공 여부(isSuccess)와 반환된 데이터(data)를 검사한다.
hook 실행 후 일정 시간이 지나야 통과되는 비동기적인 실행이라면, renderHook에서 반환되는 waitForNextUpdate함수를 통해 hook 내부 로직에서 변화가 발생하는 시점까지 기다린 후 expect 문을 실행할 수 있다.

MSW catch error docs

import { setupWorker, rest } from 'msw' 2 3const worker = setupWorker( 4 rest.post('/login', (req, res, ctx) => { 5 const { username } = req.body 6 7 return res( 8// Send a valid HTTP status code 9 ctx.status(403), 10// And a response body, if necessary 11 ctx.json({ 12 errorMessage: `User '${username}' not found`, 13 }), 14 ) 15 }), 16) 17 18worker.start()
TypeScript
복사

에러 처리

오류 응답을 조롱할 때는 요청 핸들러 내부에서 예외를 적용하기 보다는 res() 구성 체인을 사용하여 유효한 응답을 구성하는 것이 좋습니다. 이는 주로 내부 예외와 의도된 예외를 구별하기 위한 것이다.
요청 핸들러에서 발생한 예외는 서비스 작업자의 500 응답으로 처리되지만 코드에 오류가 있음을 나타내므로 해결하는 것이 좋습니다.
오류 응답을 예외가 아닌 실제 응답으로 처리함으로써 표준을 준수하고 클라이언트 코드가 유효한 오류 응답을 수신하고 처리하는지 확인합니다.
Just very recently, Typescript has been update to make error object inside catch be unknown instead of any
which means, your code looks something like this to your compiler
catch(e: unknown) { // your logic }
Plain Text
복사
Provide your own interface and save into another variable to avoid this error:
catch(e : unknown) { const u = e as YourType // your logic }
Plain Text
복사
You can still use any there, but it's not recommended.
3612
issues

AxiosError로 타입 단언 후 사용..보다는

useAxios PR

useAxios.test.ts
import { renderHook } from '@testing-library/react-hooks'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import useAxios from './useAxios'; const stubbedStocks = [ { symbol: 'AAPL', price: 1 }, { symbol: 'GOOG', price: 2 }, ]; const stubbedFetchUrl = 'api/stocksUrl-mocked'; const server = setupServer(); beforeEach(() => server.resetHandlers()); beforeAll(() => server.listen()); afterAll(() => server.close()); describe('useAxios는', () => { it('페치 후에 데이터를 반환해야 한다.', async () => { server.use( rest.get(stubbedFetchUrl, (req, res, ctx) => { return res(ctx.json(stubbedStocks)); }) ); const { result, waitForNextUpdate } = renderHook(() => useAxios(stubbedFetchUrl) ); await waitForNextUpdate(); expect(result.current).toStrictEqual({ loading: false, data: stubbedStocks, error: null, }); }); it('에러를 캐치해야 한다.', async () => { server.use( rest.get(stubbedFetchUrl, (req, res, ctx) => { return res( ctx.status(403), ctx.json({ errorMessage: 'Stocks not found' }) ); }) ); const { result, waitForNextUpdate } = renderHook(() => useAxios(stubbedFetchUrl) ); await waitForNextUpdate(); console.log(result.current); expect(result.current).toStrictEqual({ loading: false, data: null, error: 'Stocks not found', }); }); });
TypeScript
복사
useAxios.tsx
import axios from 'axios'; import { useState, useEffect } from 'react'; const axiosConfig = { headers: { 'Content-Type': 'application/json', accept: 'text/html; charset=utf-8', }, }; interface ReturnType<S> { data: S | null; loading: boolean; error: unknown; } function useAxios<S>(url: string): ReturnType<S> { const [data, setData] = useState<S | null>(null); const [loading, setLoading] = useState<boolean>(true); const [error, setError] = useState<unknown>(null); const fetchData = async () => { try { const res = await axios.get(url, axiosConfig); if (res.status === 200) { setLoading(false); setData(res.data); } else new Error(); } catch (e) { // check if the error was thrown from axios if (axios.isAxiosError(e)) { // // do something // // or just re-throw the error // // ex) throw e; setLoading(false); setError(e.response ? e.response.data.errorMessage : e.message); } else { // // do something else // // or creating a new error // // ex) throw new Error('different error than axios'); setLoading(false); setError('different error'); } } }; useEffect(() => { fetchData(); return () => setLoading(false); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { data, loading, error }; } export default useAxios;
TypeScript
복사