본문으로 건너뛰기
비공식 베타 번역

이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →

테스트 작성하기

배울 내용
  • Redux를 사용하는 앱 테스트에 대한 권장 사례
  • 테스트 구성 및 설정 예시

원칙

Redux 로직 테스트의 원칙은 React Testing Library의 접근 방식을 따릅니다:

테스트가 실제 소프트웨어 사용 방식과 유사할수록 더 높은 신뢰도를 얻을 수 있습니다. - Kent C. Dodds

작성하는 대부분의 Redux 코드는 함수이며, 그중 상당수는 순수 함수이므로 모킹 없이 테스트하기 쉽습니다. 그러나 각 Redux 코드 조각에 전용 테스트가 필요한지 고려해야 합니다. 대부분의 시나리오에서 최종 사용자는 애플리케이션에서 Redux가 사용되는지 알지 못하며 관심도 없습니다. 따라서 Redux 코드는 앱의 구현 세부사항으로 취급될 수 있으며, 많은 경우 Redux 코드에 대한 명시적인 테스트가 필요하지 않습니다.

Redux를 사용하는 앱 테스트에 대한 일반적인 조언은 다음과 같습니다:

  • 통합 테스트를 우선시하고 모든 요소가 함께 작동하도록 작성하세요. Redux를 사용하는 React 앱의 경우, 실제 store 인스턴스를 사용해 <Provider>로 테스트 대상 컴포넌트를 래핑하여 렌더링합니다. 테스트 페이지와의 상호작용은 실제 Redux 로직을 사용해야 하며, 앱 코드를 변경하지 않도록 API 호출을 모킹하고 UI가 적절히 업데이트되었는지 확인해야 합니다.

  • 필요한 경우 특히 복잡한 리듀서나 셀렉터 같은 순수 함수에 기본 단위 테스트를 사용하세요. 그러나 대부분의 경우 이들은 통합 테스트에서 다루어지는 구현 세부사항일 뿐입니다.

  • 셀렉터 함수나 React-Redux 훅을 모킹하려고 시도하지 마세요! 라이브러리에서 가져온 요소를 모킹하는 것은 취약하며, 실제 앱 코드가 제대로 작동한다는 확신을 주지 않습니다.

정보

통합 스타일 테스트를 권장하는 배경은 다음을 참고하세요:

테스트 환경 설정

테스트 러너(Test Runners)

Redux는 순수 JavaScript이므로 모든 테스트 러너로 테스트 가능합니다. 점점 더 많이 선택되는 도구는 Vitest (Redux 라이브러리 저장소에서 사용)이며, Jest도 여전히 널리 사용됩니다.

일반적으로 테스트 러너는 JavaScript/TypeScript 구문을 컴파일하도록 구성해야 합니다. 브라우저 없이 UI 컴포넌트를 테스트할 경우, 모의 DOM 환경을 제공하기 위해 테스트 러너를 JSDOM 사용으로 구성해야 할 수 있습니다.

이 문서의 예시는 Vitest 사용을 가정하지만, 어떤 테스트 러너를 사용하든 동일한 패턴이 적용됩니다.

일반적인 테스트 러너 구성 지침은 다음 자료를 참고하세요:

UI 및 네트워크 테스트 도구

Redux 팀은 Redux에 연결된 React 컴포넌트 테스트를 위해 Vitest 브라우저 모드 또는 React Testing Library (RTL) 사용을 권장합니다.

React Testing Library는 좋은 테스트 사례를 장려하는 간결하고 완전한 React DOM 테스트 유틸리티입니다. ReactDOM의 render 함수와 react-dom/tests-utils의 act를 사용합니다. (Testing Library 도구 제품군에는 다른 많은 인기 프레임워크용 어댑터도 포함됩니다.)

Vitest 브라우저 모드는 실제 브라우저에서 통합 테스트를 실행해 "모의" DOM 환경 필요성을 제거합니다(시각적 피드백 및 회귀 테스트 가능). React 사용 시 RTL과 유사한 render 유틸리티를 포함하는 vitest-browser-react도 필요합니다.

또한 네트워크 요청 모킹에는 Mock Service Worker(MSW) 사용을 권장합니다. 이를 통해 테스트 작성 시 애플리케이션 로직을 변경하거나 모킹할 필요가 없습니다.

연결된 컴포넌트와 Redux 로직 통합 테스트

Redux에 연결된 React 컴포넌트 테스트에 대한 권장 접근 방식은 모든 요소가 함께 동작하는 통합 테스트입니다. 사용자가 특정 방식으로 상호작용할 때 애플리케이션이 기대한 대로 동작하는지 검증하는 데 주안점을 둡니다.

애플리케이션 코드 예시

다음 userSlice 슬라이스, 스토어, App 컴포넌트를 가정합니다:

features/users/usersSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'
import type { RootState } from '../../app/store'

export const fetchUser = createAsyncThunk('user/fetchUser', async () => {
const response = await userAPI.fetchUser()
return response.data
})

interface UserState {
name: string
status: 'idle' | 'loading' | 'complete'
}

const initialState: UserState = {
name: 'No user',
status: 'idle'
}

const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers: builder => {
builder.addCase(fetchUser.pending, (state, action) => {
state.status = 'loading'
})
builder.addCase(fetchUser.fulfilled, (state, action) => {
state.status = 'complete'
state.name = action.payload
})
}
})

export const selectUserName = (state: RootState) => state.user.name
export const selectUserFetchStatus = (state: RootState) => state.user.status

export default userSlice.reducer
app/store.ts
import { combineReducers, configureStore } from '@reduxjs/toolkit'
import userReducer from '../features/users/userSlice'
// Create the root reducer independently to obtain the RootState and PreloadedState type
const rootReducer = combineReducers({
user: userReducer
})
export function setupStore(preloadedState?: PreloadedState) {
return configureStore({
reducer: rootReducer,
preloadedState
})
}
export type PreloadedState = Parameters<typeof rootReducer>[0]
export type RootState = ReturnType<typeof rootReducer>
export type AppStore = ReturnType<typeof setupStore>
export type AppDispatch = AppStore['dispatch']
app/hooks.ts
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
features/users/UserDisplay.tsx
import React from 'react'
import { useAppDispatch, useAppSelector } from '../../app/hooks'
import { fetchUser, selectUserName, selectUserFetchStatus } from './userSlice'

export default function UserDisplay() {
const dispatch = useAppDispatch()
const userName = useAppSelector(selectUserName)
const userFetchStatus = useAppSelector(selectUserFetchStatus)

return (
<div>
{/* Display the current user name */}
<div>{userName}</div>
{/* On button click, dispatch a thunk action to fetch a user */}
<button onClick={() => dispatch(fetchUser())}>Fetch user</button>
{/* At any point if we're fetching a user, display that on the UI */}
{userFetchStatus === 'loading' && <div>Fetching user...</div>}
</div>
)
}

이 애플리케이션에는 썽크(thunks), 리듀서(reducers), 셀렉터(selectors)가 포함됩니다. 통합 테스트 작성 시 다음 사항을 고려하여 모두 테스트할 수 있습니다:

  • 앱 최초 로딩 시 사용자 데이터가 없어야 함 → 화면에 'No user' 표시됨

  • 'Fetch user' 버튼 클릭 후 사용자 데이터 요청 시작 → 화면에 'Fetching user...' 표시됨

  • 일정 시간 후 사용자 데이터 수신 → 'Fetching user...' 사라지고 API 응답 기반 사용자 이름 표시됨

위 시나리오에 초점을 맞춘 테스트 작성으로 애플리케이션 모킹을 최소화할 수 있습니다. 또한 사용자 사용 패턴과 동일한 상호작용 시 앱의 핵심 동작이 기대한 대로 수행됨을 확신할 수 있습니다.

컴포넌트 테스트 시 DOM에 render로 렌더링한 후, 사용자 사용 패턴과 일치하는 상호작용에 대한 애플리케이션 응답을 검증합니다.

재사용 가능 테스트 렌더 함수 설정

React Testing Library의 render 함수는 React 엘리먼트 트리를 받아 컴포넌트를 렌더링합니다. 실제 앱과 마찬가지로 Redux 연결 컴포넌트는 실제 Redux 스토어가 설정된 React-Redux <Provider> 컴포넌트로 래핑되어야 합니다.

또한 테스트 간 상태 누출을 방지하려면 매 테스트마다 별도의 Redux 스토어 인스턴스를 생성해야 합니다. 동일 스토어 인스턴스 재사용이나 상태 리셋은 피해야 합니다.

React Testing Library 설정 문서에 설명된 대로, render 함수의 wrapper 옵션을 활용해 새 Redux 스토어를 생성하고 <Provider>를 렌더링하는 커스텀 renderWithProviders 함수를 구현하면 매 테스트마다 스토어 생성과 Provider 설정을 반복하지 않아도 됩니다.

커스텀 렌더 함수는 다음 기능을 제공해야 합니다:

  • 호출 시마다 새 Redux 스토어 인스턴스 생성 (초기값 설정용 선택적 preloadedState 포함)

  • 이미 생성된 Redux 스토어 인스턴스를 전달하는 방법

  • RTL의 원본 render 함수에 추가 옵션 전달

  • 테스트 대상 컴포넌트를 자동으로 <Provider store={store}>로 래핑

  • 추가 액션 디스패치나 상태 확인이 필요한 경우 스토어 인스턴스 반환

편의를 위해 사용자 인스턴스도 설정해 봅시다.

일반적인 커스텀 렌더 함수 설정 예시:

utils/test-utils.tsx
import React, { PropsWithChildren } from 'react'
import { render } from '@testing-library/react'
import type { RenderOptions } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { Provider } from 'react-redux'

import type { AppStore, RootState, PreloadedState } from '../app/store'
import { setupStore } from '../app/store'

// This type interface extends the default options for render from RTL, as well
// as allows the user to specify other things such as preloadedState, store.
interface ExtendedRenderOptions
extends Omit<RenderOptions, 'queries' | 'wrapper'> {
preloadedState?: PreloadedState
store?: AppStore
}

export function renderWithProviders(
ui: React.ReactElement,
extendedRenderOptions: ExtendedRenderOptions = {}
) {
const {
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = setupStore(preloadedState),
...renderOptions
} = extendedRenderOptions

const Wrapper = ({ children }: PropsWithChildren) => (
<Provider store={store}>{children}</Provider>
)

// Return an object with the store, user, and all of RTL's query functions
return {
store,
user: userEvent.setup(),
...render(ui, { wrapper: Wrapper, ...renderOptions })
}
}

컴포넌트 통합 테스트 작성

실제 테스트 파일은 커스텀 render 함수를 사용해 Redux 연결 컴포넌트를 렌더링해야 합니다. 테스트 대상 코드에 네트워크 요청이 포함된 경우 MSW를 구성해 적절한 테스트 데이터로 예상 요청을 모의 처리해야 합니다.

features/users/tests/UserDisplay.test.tsx
import React from 'react'
import { beforeAll, afterEach, afterAll, test, expect } from 'vitest'
import { http, HttpResponse, delay } from 'msw'
import { setupServer } from 'msw/node'
import { screen } from '@testing-library/react'
// We're using our own custom render function and not RTL's render.
import { renderWithProviders } from '../../../utils/test-utils'
import UserDisplay from '../UserDisplay'

// We use msw to intercept the network request during the test,
// and return the response 'John Smith' after 150ms
// when receiving a get request to the `/api/user` endpoint
export const handlers = [
http.get('/api/user', async () => {
await delay(150)
return HttpResponse.json('John Smith')
})
]

const server = setupServer(...handlers)

// Enable API mocking before tests.
beforeAll(() => server.listen())

// Reset any runtime request handlers we may add during the tests.
afterEach(() => server.resetHandlers())

// Disable API mocking after the tests are done.
afterAll(() => server.close())

test('fetches & receives a user after clicking the fetch user button', async () => {
const { user } = renderWithProviders(<UserDisplay />)

// should show no user initially, and not be fetching a user
expect(screen.getByText(/no user/i)).toBeInTheDocument()
expect(screen.queryByText(/Fetching user\.\.\./i)).not.toBeInTheDocument()

// after clicking the 'Fetch user' button, it should now show that it is fetching the user
await user.click(screen.getByRole('button', { name: /Fetch user/i }))
expect(screen.queryByText(/no user/i)).not.toBeInTheDocument()
expect(screen.getByText(/Fetching user\.\.\./i)).toBeInTheDocument()

// after some time, the user should be received
expect(await screen.findByText(/John Smith/i)).toBeInTheDocument()
expect(screen.queryByText(/no user/i)).not.toBeInTheDocument()
expect(screen.queryByText(/Fetching user\.\.\./i)).not.toBeInTheDocument()
})

이 테스트에서는 Redux 코드를 직접 테스트하지 않고 구현 세부사항으로 취급했습니다. 결과적으로 구현을 리팩터링하더라도 테스트는 계속 통과하며 오탐(false negative)을 방지합니다. 상태 구조를 변경하거나 슬라이스를 RTK-Query로 전환하거나 Redux를 완전히 제거해도 테스트는 통과합니다. 코드 변경 후 테스트가 실패한다면 실제 애플리케이션에 문제가 있다는 강력한 신뢰를 얻을 수 있습니다.

초기 테스트 상태 준비

많은 테스트는 컴포넌트 렌더링 전 Redux 스토어에 특정 상태가 이미 존재해야 합니다. 커스텀 렌더 함수로 이를 구현하는 방법은 여러 가지입니다.

첫 번째 옵션은 커스텀 렌더 함수에 preloadedState 인수를 전달하는 것입니다:

TodoList.test.tsx
test('Uses preloaded state to render', () => {
const initialTodos = [{ id: 5, text: 'Buy Milk', completed: false }]

const { getByText } = renderWithProviders(<TodoList />, {
preloadedState: {
todos: initialTodos
}
})
})

두 번째 옵션은 커스텀 Redux 스토어를 먼저 생성하고 원하는 상태를 구축하기 위해 액션을 디스패치한 후 해당 스토어 인스턴스를 전달하는 것입니다:

TodoList.test.tsx
test('Sets up initial state state with actions', () => {
const store = setupStore()
store.dispatch(todoAdded('Buy milk'))

const { getByText } = renderWithProviders(<TodoList />, { store })
})

커스텀 렌더 함수에서 반환된 객체에서 store를 추출한 후 테스트 중에 추가 액션을 디스패치할 수도 있습니다.

Vitest 브라우저 모드

재사용 가능한 테스트 렌더 함수 설정

RTL과 유사하게 Vitest 브라우저 모드는 실제 브라우저에서 컴포넌트 렌더링에 사용할 수 있는 render 함수를 제공합니다. 다만 React-Redux 앱 테스트 시 렌더링된 트리에 <Provider>가 포함되도록 해야 합니다.

위에서 설명한 RTL 사용자 정의 렌더 함수와 유사하게, 컴포넌트를 <Provider>로 래핑하고 Redux 스토어를 설정하는 사용자 정의 렌더 함수를 생성할 수 있습니다.

utils/test-utils.tsx
import React, { PropsWithChildren } from 'react'
import { render } from 'vitest-browser-react'
import type { RenderOptions } from 'vitest-browser-react'
import { Provider } from 'react-redux'

import type { AppStore, RootState, PreloadedState } from '../app/store'
import { setupStore } from '../app/store'

// This type interface extends the default options for render from vitest-browser-react, as well
// as allows the user to specify other things such as preloadedState, store.
interface ExtendedRenderOptions extends Omit<RenderOptions, 'wrapper'> {
preloadedState?: PreloadedState
store?: AppStore
}

export async function renderWithProviders(
ui: React.ReactElement,
extendedRenderOptions: ExtendedRenderOptions = {}
) {
const {
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = setupStore(preloadedState),
...renderOptions
} = extendedRenderOptions

const Wrapper = ({ children }: PropsWithChildren) => (
<Provider store={store}>{children}</Provider>
)

const screen = await render(ui, { wrapper: Wrapper, ...renderOptions })
// Return an object with the store, and the result of rendering
return {
store,
...screen
}
}

편의를 위해 설정 파일에서 page에 이를 첨부할 수도 있습니다:

setup.ts
import { renderWithProviders } from './utils/test-utils'
import { page } from 'vitest/browser'

page.extend({ renderWithProviders })

declare module 'vitest/browser' {
interface BrowserPage {
renderWithProviders: typeof renderWithProviders
}
}

그러면 테스트에서 RTL과 유사하게 사용할 수 있습니다:

features/users/tests/UserDisplay.test.tsx
import React from 'react'
import { test, expect } from 'vitest'
import { page } from 'vitest/browser'
import UserDisplay from '../UserDisplay'

test('fetches & receives a user after clicking the fetch user button', async () => {
const { store, ...screen } = await page.renderWithProviders(<UserDisplay />)

const noUserText = screen.getByText(/no user/i)
const fetchingUserText = screen.getByText(/Fetching user\.\.\./i)
const userNameText = screen.getByText(/John Smith/i)

// should show no user initially, and not be fetching a user
await expect.element(noUserText).toBeInTheDocument()
await expect.element(fetchingUserText).not.toBeInTheDocument()

// after clicking the 'Fetch user' button, it should now show that it is fetching the user
await screen.getByRole('button', { name: /fetch user/i }).click()
await expect.element(noUserText).not.toBeInTheDocument()
await expect.element(fetchingUserText).toBeInTheDocument()

// after some time, the user should be received
await expect.element(userNameText).toBeInTheDocument()
await expect.element(noUserText).not.toBeInTheDocument()
await expect.element(fetchingUserText).not.toBeInTheDocument()
})

개별 함수 유닛 테스트

모든 Redux 로직이 함께 동작하는 통합 테스트를 기본으로 권장하지만, 경우에 따라 개별 함수에 대한 유닛 테스트를 작성할 수도 있습니다.

리듀서(Reducers)

리듀서는 이전 상태에 액션을 적용한 후 새로운 상태를 반환하는 순수 함수입니다. 대부분의 경우 리듀서는 명시적 테스트가 필요 없는 구현 세부사항입니다. 그러나 특히 복잡한 로직이 포함되어 유닛 테스트가 필요한 경우 리듀서는 쉽게 테스트할 수 있습니다.

리듀서는 순수 함수이므로 테스트가 직관적입니다. 특정 입력 stateaction으로 리듀서를 호출한 후 결과 상태가 기대값과 일치하는지 확인하면 됩니다.

예제

import { createSlice, PayloadAction } from '@reduxjs/toolkit'

export type Todo = {
id: number
text: string
completed: boolean
}

const initialState: Todo[] = [{ text: 'Use Redux', completed: false, id: 0 }]

const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
todoAdded(state, action: PayloadAction<string>) {
state.push({
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: action.payload
})
}
}
})

export const { todoAdded } = todosSlice.actions

export default todosSlice.reducer

다음과 같이 테스트할 수 있습니다:

import { test, expect } from 'vitest'
import reducer, { todoAdded, Todo } from './todosSlice'

test('should return the initial state', () => {
expect(reducer(undefined, { type: 'unknown' })).toEqual([
{ text: 'Use Redux', completed: false, id: 0 }
])
})

test('should handle a todo being added to an empty list', () => {
const previousState: Todo[] = []

expect(reducer(previousState, todoAdded('Run the tests'))).toEqual([
{ text: 'Run the tests', completed: false, id: 0 }
])
})

test('should handle a todo being added to an existing list', () => {
const previousState: Todo[] = [
{ text: 'Run the tests', completed: true, id: 0 }
]

expect(reducer(previousState, todoAdded('Use Redux'))).toEqual([
{ text: 'Run the tests', completed: true, id: 0 },
{ text: 'Use Redux', completed: false, id: 1 }
])
})

셀렉터(Selectors)

셀렉터도 일반적으로 순수 함수이므로 리듀서와 동일한 기본 접근 방식으로 테스트할 수 있습니다: 초기값을 설정하고, 해당 입력값으로 셀렉터 함수를 호출한 후 결과가 예상 출력과 일치하는지 확인합니다.

그러나 대부분의 셀렉터는 마지막 입력을 기억하기 위해 메모이제이션됩니다. 테스트에서 사용 위치에 따라 새 결과를 생성할 것으로 예상했으나 캐시된 값을 반환하는 경우에 주의해야 합니다.

액션 생성자 & 썽크

Redux에서 액션 생성자(action creator)는 일반 객체를 반환하는 함수입니다. 액션 생성자를 수동으로 작성하지 말고, 대신 @reduxjs/toolkitcreateSlice로 자동 생성하거나 createAction을 통해 생성할 것을 권장합니다. 따라서 액션 생성자를 별도로 테스트할 필요는 없습니다 (Redux Toolkit 관리자가 이미 테스트를 완료했습니다!).

액션 생성자의 반환 값은 애플리케이션 내부의 구현 세부 사항으로 간주되며, 통합 테스트 방식을 따를 경우 명시적인 테스트가 필요하지 않습니다.

Redux Thunk를 사용하는 썽크(thunk)의 경우에도 수동 작성 대신 @reduxjs/toolkitcreateAsyncThunk를 사용할 것을 권장합니다. 썽크는 자체 라이프사이클에 따라 pending, fulfilled, rejected 액션 유형을 자동으로 디스패치합니다.

썽크 동작은 애플리케이션의 구현 세부 사항으로 간주하며, 썽크를 격리된 상태로 테스트하기보다는 이를 사용하는 컴포넌트 그룹(또는 전체 앱)을 테스트하는 방식으로 커버할 것을 권장합니다.

msw, miragejs, jest-fetch-mock, fetch-mock 등의 도구를 사용해 fetch/xhr 수준에서 비동기 요청을 모킹할 것을 권장합니다. 이렇게 하면 테스트에서 썽크 로직을 변경할 필요가 없습니다. 썽크는 "실제" 비동기 요청을 시도하지만 요청이 가로채어질 뿐입니다. 썽크 동작을 내부에 포함하는 컴포넌트 테스트 예시는 "통합 테스트" 예제를 참조하세요.

정보

액션 생성자나 썽크에 대한 단위 테스트를 작성해야 한다면, Redux Toolkit의 createActioncreateAsyncThunk 테스트 구현을 참조하세요.

미들웨어(Middleware)

미들웨어 함수는 Redux에서 dispatch 호출의 동작을 감싸므로, 수정된 동작을 테스트하려면 dispatch 호출을 모킹해야 합니다.

예제

먼저 실제 redux-thunk와 유사한 미들웨어 함수가 필요합니다.

const thunkMiddleware =
({ dispatch, getState }) =>
next =>
action => {
if (typeof action === 'function') {
return action(dispatch, getState)
}

return next(action)
}

가짜 getState, dispatch, next 함수를 생성해야 합니다. Jest에서는 jest.fn()으로 스텁(stub)을 생성하지만, 다른 테스트 프레임워크에서는 Sinon을 사용할 수 있습니다.

invoke 함수는 Redux와 동일한 방식으로 미들웨어를 실행합니다.

const create = () => {
const store = {
getState: jest.fn(() => ({})),
dispatch: jest.fn()
}
const next = jest.fn()

const invoke = action => thunkMiddleware(store)(next)(action)

return { store, next, invoke }
}

미들웨어가 적절한 시점에 getState, dispatch, next 함수를 호출하는지 테스트합니다.

test('passes through non-function action', () => {
const { next, invoke } = create()
const action = { type: 'TEST' }
invoke(action)
expect(next).toHaveBeenCalledWith(action)
})

test('calls the function', () => {
const { invoke } = create()
const fn = jest.fn()
invoke(fn)
expect(fn).toHaveBeenCalled()
})

test('passes dispatch and getState', () => {
const { store, invoke } = create()
invoke((dispatch, getState) => {
dispatch('TEST DISPATCH')
getState()
})
expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH')
expect(store.getState).toHaveBeenCalled()
})

경우에 따라 getStatenext의 다른 모의 구현을 사용하도록 create 함수를 수정해야 할 수 있습니다.

추가 정보

  • React Testing Library: React 컴포넌트 테스트를 위한 경량 솔루션입니다. react-dom 및 react-dom/test-utils 위에 유틸리티 함수를 제공하며, "테스트가 소프트웨어 사용 방식과 유사할수록 더 큰 신뢰도를 제공한다"는 핵심 원칙을 따릅니다.

  • Blogged Answers: The Evolution of Redux Testing Approaches: 마크 에릭슨(Mark Erikson)이 '분리'에서 '통합'으로 진화한 Redux 테스팅 접근법에 대한 생각

  • Testing Implementation Details: 켄트 C. 닷스(Kent C. Dodds)가 구현 세부 사항 테스트를 피해야 하는 이유에 대해 설명한 블로그 게시물