본문으로 건너뛰기

"testing-library" 태그로 연결된 4개 게시물개의 게시물이 있습니다.

모든 태그 보기

· 약 5분
Mitchell

현재 앱의 컬러테마를 다루는 useThemeMode 커스텀 훅을 아래와 같이 테스트하려고 합니다. 기본 값은 'light'이며, changeTheme을 호출하여 테마를 변경할 수 있습니다.

const renderUseThemeMode = () => {
const { result } = renderHook(() => useThemeMode())

return result.current
}

describe('Test hook', () => {
test('render hook correctly', () => {
const { currentTheme, changeTheme } = renderUseThemeMode()

expect(currentTheme).toBe('light')

act(() => {
changeTheme('dark')
})

expect(currentTheme).toBe('dark')
})
})

첫번째 assertion에서는 예상대로 현재 테마가 기본 값인 'light'라고 통과하게 됩니다. 그러나 두번째 assertion에서 테마가 'dark'로 변경되지 않아 테스트가 최종적으로 실패하는 결과를 얻게 됩니다.

상태의 변화를 발생시키는 함수인 changeThemeact의 내부에서 실행하여 분명히 상태 변화가 발생하고, 테스트가 통과해야할텐데요. 왜 상태변화를 추적하지 못하고 있는걸까요?


문제의 원인

문제의 원인은 renderUseThemeMode 함수에 있습니다. 여러 테스트케이스에 걸쳐 편하게 훅을 렌더링하기 위해 만들어둔 함수인데요. 내부 구현을 보면 renderHook의 반환 값 중 result.current를 반환하는 것을 볼 수 있습니다. 여기가 바로 문제가 되는 부분인데요. 초기 상태에 대한 값이 result.current에는 들어있겠지만, 상태가 변경되더라도 이전의 값을 유지하기 때문에 변경된 상태로 assertion을 하면 실패하게 됩니다.


해결방법

해결방법은 아주 간단하지만 조금은 불편할 수 있는데요. result.current를 별도의 변수에 할당하지 않고 사용하면 상태 변화를 정상적으로 추적할 수 있게 됩니다. 이에 따라 renderUseThemeMode 렌더함수를 사용하지 않고, renderHook을 직접 호출하도록 변경하였습니다.

describe('Test hook', () => {
test('render hook correctly', () => {
const { result } = renderHook(() => useThemeMode())

expect(result.current.currentTheme).toBe('light')

act(() => {
result.current.changeTheme('dark')
})

expect(result.current.currentTheme).toBe('dark')
})
})

이제 정상적으로 상태 변화를 추적하여, 마지막 assertion을 통과할 수 있게 됩니다.


왜 그럴까?

그렇다면 왜 result.current을 다른 변수에 선언하면 상태 변화를 추적하지 못할까요? 우선 result.current의 정체가 무엇인지 renderHook 함수의 구현을 살펴보겠습니다.

function renderHook(renderCallback, options = {}) {
const {initialProps, ...renderOptions} = options
const result = React.createRef()

// 테스트용 함수 컴포넌트 생성
// 결과를 ref의 current로 전달
function TestComponent({renderCallbackProps}) {
// 전달한 커스텀 훅
const pendingResult = renderCallback(renderCallbackProps)

// 의존성 배열을 설정하지 않았으므로 매 렌더링마다 result.current의 값이 변경됩니다.
React.useEffect(() => {
result.current = pendingResult
})

return null
}

// 테스트 컴포넌트를 렌더, 즉 커스텀 훅을 실행합니다.
const {rerender: baseRerender, unmount} = render(
<TestComponent renderCallbackProps={initialProps} />,
renderOptions,
)

// 리렌러더 함수 생성
function rerender(rerenderCallbackProps) {
return baseRerender(
<TestComponent renderCallbackProps={rerenderCallbackProps} />,
)
}

return {result, rerender, unmount}
}

커스텀 훅의 변경된 상태가 React.creatRef로 생성된 result.current로 반영되도록 설계 되어있습니다. result.current의 참조는 React.creatRef로 생성되어 렌더링이나 상태변화에도 참조가 바뀌지 않습니다. 참조는 바뀌지 않는데, 그 안의 값은 바뀌기 때문에 renderHook의 result.current로 참조하는 경우 상태 변경에 대한 값을 지속해서 추적할 수 있게 됩니다.

· 약 16분
Mitchell

들어가기 전에

이전 글에서는 LoginForm 컴포넌트에 대한 단위테스트(Unit test) 코드를 작성하면서 리팩토링까지 해보았습니다. 이번에는 여러 컴포넌트의 상호작용에 대한 통합테스트를 작성하겠습니다.

정보

단위테스트에 대한 내용은 아래 링크를 통해서 확인하실 수 있으며, 이 글을 이해하는데에도 도움이 됩니다.
단위테스트

선수지식

이 글에서는 사용되는 기술에 대한 상세한 사용법은 설명하지 않습니다.

  • React
  • React-toastify
  • React Router, TanStack Query
  • Vitest, testing-library, msw
  • Vite

통합테스트란?

이름 그대로 통합적으로 테스트하는 개념의 테스트입니다. 이전 글에서는 LoginForm 컴포넌트에 대해 한정적으로 테스트를 구현하였었는데요. 그러한 작은 단위를 테스트하는 것을 단위테스트(Unit test)라고 합니다. 반면에 통합테스트(Integration test)는 여러 작은 컴포넌트로 구성된 큰 컴포넌트나 페이지 같이 통합된 형태를 테스트하는 것을 목적으로 합니다. 통합테스트의 주요 목표는 두 가지로 분류해 볼 수 있습니다.

  • 서버와의 통신에 대한 테스트
  • 컴포넌트 간의 상호작용에 대한 테스트

그래서 이번에는 서버와의 통신에 대한 테스트로 useLogin 커스텀훅을 테스트해보고 마지막으로 LoginFormuseLogin 등 컴포넌트와 훅 등이 상호작용하는 LoginPage에 대한 테스트를 진행하겠습니다. 테스트를 진행하면서 발생하는 트러블들을 해결하는 과정을 통해 msw 사용법에 대해서도 익히실 수 있습니다.

· 약 13분
Mitchell

들어가기 전에

늘어가는 기능과 화면에 비례해서 버그 또한 늘어나기 마련입니다. 그런데 그 버그를 수정하면 또 다른 곳이 터져버리는 불상사가 눈앞에 펼쳐지게 됩니다. 사람은 실수를 하기 마련이고 그것을 줄이기 위해서는 더 많은 시간을 쏟아야 합니다. 이러한 이유 때문이더라도 테스트코드는 결국 필요하게 됩니다.

따라서 이 글에서는 React로 작성된 <LoginForm /> 컴포넌트에 대해 단위테스트를 작성하고, 그 과정에서 기존 코드의 문제점을 분석하고 테스트 가능한 코드로 리팩토링 하겠습니다.

· 약 17분
Mitchell

들어가기 전에

컴포넌트 단위 테스트를 작성할 때의 주요 관심사가 무엇일까요? 서버나 외부에서 데이터가 정상적으로 전달되었다는 것을 가정하고, UI가 의도대로 렌더링되는가에 관심을 둡니다.

그런데 통합테스트 단계에서는 그러한 데이터가 전달되는 상황을 Mocking 해야할 필요가 있습니다. 그래서 msw와 같은 라이브러리를 활용하여 하여 테스트를 서버로부터 오는 단방향 통신에 대해 Mocking 하여 통합테스트를 마무리할 수 있게 됩니다.

그러나 msw에서는 WebSocket과 같은 양방향 통신에 대한 지원이 존재하지 않아 채팅과 같은 실시간서비스의 통합테스팅을 작성하는데는 적합하지 않습니다. 따라서 Socket.IO가 제공하는 테스팅 예제를 활용하여 서버와의 양방향 통신을 Mocking 할 수 있는 방법을 공유해보겠습니다.