main loading
api 상태 관리는 react-query 이용한다.
# 리액트 쿼리 설치
yarn add @tanstack/react-query
# 실험용 버전 사용
yarn add @tanstack/react-query-next-experimental
react-query 에서는 next.js 와 함께 사용하기 위한 방법을 안내하고 있는데 이중 실험용으로 제공되는 방식을 사용 할 것이다
layout.tsx 파일에 적용한다.// 파일경로 <projectName>/src/app/providers.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import * as React from 'react'
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
})
}
let browserQueryClient: QueryClient | undefined = undefined
function getQueryClient() {
if (typeof window === 'undefined') {
return makeQueryClient()
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
}
export function Providers(props: { children: React.ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
<ReactQueryStreamedHydration>
{props.children}
</ReactQueryStreamedHydration>
</QueryClientProvider>
)
}
// src/app/layout.tsx
import { Providers } from "./provider";
...
// 프로바이더 적용
<Providers>
{children}
</Providers>
이제 react-query 가 정상동작하는지 확인하기 위한 hook 파일을 만들고 이전 포스트에서 만들어둔 page.tsx 파일에 쿼리를 추가해준다.
// 파일경로 <projectName>/src/apis/useGetUserInfoServer.ts
// rsc 테스트용
import { useSuspenseQuery } from "@tanstack/react-query";
export default function useGetUserInfoServer() {
// 서버 테스트를 위한 suspenseQuery
return useSuspenseQuery({
queryKey: ["useGetUserInfo-server"],
queryFn: () => fetch("http://localhost:3000/api/user-server", {
method: "GET"
})
})
}
// 파일경로 <projectName>/src/apis/useGetUserInfoClient.ts
// client 테스트용
import { useQuery } from "@tanstack/react-query";
export default function useGetUserInfoClient() {
// 클라이언트 테스트를 위한 일반 query
return useQuery({
queryKey: ["useGetUserInfo-client"],
queryFn: () => fetch("http://localhost:3000/api/user-client", {
method: "GET"
}),
// 클라이언트 컴포넌트에서 테스트하기 위한 enabled false
enabled: false
})
}
// 파일경로 <projectName>/src/app/page.tsx
"use client";
import useGetUserInfoClient from "@/apis/useGetUserInfoClient";
import useGetUserInfoServer from "@/apis/useGetUserInfoServer";
import { useState } from "react";
export default function Page() {
const [count, setCount] = useState(0);
// 서버용 테스트
const { data } = useGetUserInfoServer();
// 클라이언트용 테스트
const { data: clientData, refetch } = useGetUserInfoClient();
const handleClickButton = () => {
setCount((prev) => prev + 1);
};
console.log(data);
return (
<div data-testid="home">
<button type="button" onClick={handleClickButton} data-testid="button">
click me
</button>
<span data-testid="count-section">{count}</span>
<br />
<button type="button" onClick={() => refetch()}>
refetch
</button>
</div>
);
}
서버에서 발생하는 404 에러는 터미널에서 확인 클라이언트에서 발생하는 404 에러는 브라우저의 refetch 버튼을 클릭하고 개발자 도구를 열어 확인

클라이언트와 서버 모두에서 404에러가 나는 것을 확인 했으면 msw를 적용하요 격리된 테스트 환경을 구성해보자
msw를 선택한 이유는 (https://tkdodo.eu/blog/testing-react-query#mocking-network-requests) 가이드에 나와 있는 Stop mocking fetch 섹션에서 kent 님이 작성한 글을 참고했다. (https://kentcdodds.com/blog/stop-mocking-fetch)# msw 설치
yarn add msw@latest --save-dev
# msw init 진행 - next.js ./public 폴더에
npx msw init ./public
msw 에서 필요한 클라이언트 환경(browser)과 서버환경(node)에서 동작가능한 파일을 생성하고 mock 파일을 작성한다.
// 파일경로 <projectName>/src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
// 파일경로 <projectName>/src/mocks/node.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
// 파일경로 <projectName>/src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
// response 내용 작업 url은 react-query 의 fetch 와 동일하게 가져간다
export const handlers = [
http.get('http://localhost:3000/api/user-client', () => {
return HttpResponse.json({
id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
firstName: 'John',
lastName: 'Maverick',
})
}),
http.get('http://localhost:3000/api/user-server', () => {
return HttpResponse.json({
id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
firstName: 'John',
lastName: 'Maverick',
})
}),
]
// 파일경로 <projectName>/src/app/MockProvider.tsx
"use client";
import { useEffect } from "react";
export default function MockProvider({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
useEffect(() => {
async function enableApiMocking() {
if (typeof window !== "undefined") {
const { worker } = await import("../mocks/browser");
await worker.start();
}
}
enableApiMocking();
}, []);
return <>{children}</>;
}
// 위에 작성된 MockProvider를 적용한다
// 파일경로 <projectName>/src/app/layout.tsx
function layout() {
...
return (
<MockProvider>
<Providers>
{children}
</Providers>
</MockProvider>
);
}
msw 가 next.js에 대한 구성을 완벽하게 지원하지 않기때문에 next.js의 instrumentation 기능을 사용한다.// 파일경로 <projectName>/src/instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { server } = await import('./mocks/node')
server.listen()
}
}
// 파일경로 <projectName>/next.config.mjs
experimental: {
...
instrumentationHook: true,
...
},
각각의 api 들이 __mockData__를 정상적으로 받아오는 것을 확인한다

next.js에서 react-query를 적용하고 msw를 이용하여 격리된 개발환경을 구성하였으나 테스트는 다시 붉은색으로 변했다
정상적인 상황이며
jest는 현재msw와react-query를 모르는 상태이기 때문

next.js 에서 jest 환경을 구성react-query 라이브러리 설치msw 구성참고 문서