Notice
Recent Posts
Recent Comments
Link
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
Archives
Today
Total
관리 메뉴

효뚜르팝의 Dev log

renderHook을 사용한 hook 테스트 코드 작성 하기 본문

TIL

renderHook을 사용한 hook 테스트 코드 작성 하기

hyodduru 2023. 10. 18. 21:25

최근에 회사에서 처음으로 hook 테스트코드를 작성해보았다. 

 

데이터베이스에 저장된 유저의 정보를 기반으로 특정한 값을 추출해주는 훅이었는데, 간헐적으로 데이터베이스에 잘못된 정보가 입력되어있는 경우가 있어서, 잘못된 정보가 들어갔을 때에는 기본 값으로 설정한 값을 추출할 수있도록 처리한 로직이 잘 동작하는지 테스트해보기 위함이었다. 

 

 

사용한 기술 스택은 아래와 같다. 

 

  • next.js 
  • jest 
  • graphql

 

 

테스트를 하고 싶었던 시나리오는 대략적으로 아래와 같다. 

 

 

  • 유저의 정보가 데이터베이스에 정상적으로 입력이 된 경우
    • 유저 정보에 기반한 특정한 값이 정상적으로 추출이 된다.
  • 유저의 정보가 데이터베이스에 정상적으로 입력이 되지 않은 경우
    • 기본으로 설정된 특정한 값이 정상적으로 추출이 된다. 

 

즉 데이터베이스에 정보가 정상적으로 잘 입력되었다면 그에 맞는 값이 추출이 되고, 잘 입력되지 않았다면 기본값으로 설정한 값을 fallback으로 추출되도록 처리한 사항이 잘 동작하는지를 테스트하고 싶었다. 

 

 

 

hook을 mocking하기 위해 jest에서 제공하는 renderHook을 사용했다. 

 

이것으로 hook을 mocking 했는데, 코드는 대략적으로 아래와 같다.

 

import { graphql } from 'msw';

import useExample from './useExample';

import { server } from '__test__/testing-utils/mock-server/server';


import {
  UseExampleQuery,
  UseExampleQueryVariables,
} from '@generated/graphql';


// 예시로 작성한 코드입니다. 
describe('useExample', () => {
  describe('데이터 베이스에 사용자 정보가 정상적으로 입력되어 있지 않음', () => {
    beforeEach(async () => {
      server.use(
        graphql.query<UseExampleQuery, UseExampleQueryVariables>(
          'UseExampleQuery',
          (_req, res, ctx) => {
            const user = {id : 'id', grade : 5 };

            return res.once(
              ctx.data({
                me: user,
              })
            );
          }
        )
      );
    });

    it('exampleItems은 학년 하나의 값으로 구성된 배열로 추출된다.', async () => {
      const { result } = renderHook(
        () => useUserGradeItems());

      const { exampleItems } = result.current;

      const validExampleValues = ['first'];

      expect(exampleItems).toEqual(validGradeValues);
    });
  });

  describe('데이터 베이스에 사용자의 정보가 정상적으로 입력되어 있음', () => {
    beforeEach(() => {
      server.use(
        graphql.query<UseExampleQuery, UseExapmleQueryVariables>(
          'UseExampleQuery',
          (req, res, ctx) => {
            const user = {id : 'id', grade : 'something wrong'};
      
            return res.once(
              ctx.data({
                me: user,
              })
            );
          }
        )
      );
    });

    it('exapmleItems의 값은 기본으로 설정한 학련 목록 값으로 추출된다.', async () => {
      const { result } = renderHook(
        () => useUserGradeItems());

      const { exampleItems } = result.current;

      const validExampleValues = [
    'first', 'second', 'third'
      ];

      expect(exampleItems).toEqual(validExampleValues);
    });
  });
});

 

그런데 위 코드가 내가 의도한 대로 동작하지 않았다. 

 

beforEach에서 server를 mocking을 하기도 전에 renderHook이 동작을 해서, 데이터가 정상적으로 추출이 되지가 않았다. 

 

 

이 때 아래의 자료를 참고해서 renderHook에서 내장되어 있는 waitForNextUpdate 를 사용했다. 

출처 : https://stackoverflow.com/questions/71084181/waitfornextupdate-of-renderhook-of-react-testing-library-timeout

 

WaitForNextUpdate of renderHook of react-testing-library timeout

I'm testing a custom hook with react-testing-library which basically does this: function useHook() { const [state, setState] = useState(); const fetch = async () => { const response =...

stackoverflow.com


waitForNextUpdate 는 위에 있는 코드가 다 실행될때까지 기다려주는 함수이다. 

 

사용법은 간단하다. 

 

server mocking을 할 때까지 기다리고 싶은 구간에 아래의 코드를 적어주면 된다. 

 

const { waitForNextUpdate } = renderHook(() => useExample());

await waitForNextUpdate();

 

 

위 코드를 반영한 코드는 아래와 같다. 

 

import { graphql } from 'msw';

import useExample from './useExample';

import { server } from '__test__/testing-utils/mock-server/server';

import {
  UseExampleQuery,
  UseExampleQueryVariables,
} from '@generated/graphql';

// 예시로 작성한 코드입니다. 
describe('useExample', () => {
  describe('데이터 베이스에 사용자 정보가 정상적으로 입력되어 있지 않음', () => {
    beforeEach(async () => {
      server.use(
        graphql.query<UseExampleQuery, UseExampleQueryVariables>(
          'UseExampleQuery',
          (_req, res, ctx) => {
            const user = {id : 'id', grade : 5 };

            return res.once(
              ctx.data({
                me: user,
              })
            );
          }
        )
      );
    });

    it('exampleItems은 학년 하나의 값으로 구성된 배열로 추출된다.', async () => {
      const { result, waitForNextUpdate } = renderHook(
        () => useUserGradeItems());

      await waitForNextUpdate();
      
      const { exampleItems } = result.current;

      const validExampleValues = ['first'];

      expect(exampleItems).toEqual(validGradeValues);
    });
  });

  describe('데이터 베이스에 사용자의 정보가 정상적으로 입력되어 있음', () => {
    beforeEach(() => {
      server.use(
        graphql.query<UseExampleQuery, UseExapmleQueryVariables>(
          'UseExampleQuery',
          (req, res, ctx) => {
            const user = {id : 'id', grade : 'something wrong'};
      
            return res.once(
              ctx.data({
                me: user,
              })
            );
          }
        )
      );
    });

    it('exapmleItems의 값은 기본으로 설정한 학련 목록 값으로 추출된다.', async () => {
      const { result, waitForNextUpdate } = renderHook(
        () => useUserGradeItems());

      await waitForNextUpdate();

      const { exampleItems } = result.current;

      const validExampleValues = [
    'first', 'second', 'third'
      ];

      expect(exampleItems).toEqual(validExampleValues);
    });
  });
});

 

 

정말 신기하게도 서버 데이터가 다 받아진 이후에 이후 코드가 실행이 되어 의도한 대로 테스트 코드가 정상동작하는 것을 확인했다! 

 

신기한 기능을 알아내 신나서 기록해봅니다,,,