Skip to content

Context는 어떻게 테스트 해야 하는가? #61

@Kwakcena

Description

@Kwakcena

Context 컴포넌트는 어떻게 테스트를 해야 할까?

나는 user가 자신이 올린 상품을 삭제할 때, "삭제 하시겠습니까?" 라는 confirmation 을 수행하고 싶었다. 그래서 다음과 같이 Context를 만들어서 사용하였다.

import React, { createContext, useState, useRef } from 'react';

import ConfirmDialog from '../components/presentational/ConfirmDialog';

const ConfirmationContext = createContext({});

const ConfirmationProvider = ({ children }) => {
  const [isOpen, setIsOpen] = useState(false);
  const [confirmForm, setConfirmForm] = useState({
    title: '',
    content: '',
  });

  const { title, content } = confirmForm;

  const resolver = useRef();

  const handleShowDialog = () => {
    setIsOpen(true);
    return new Promise((resolve) => {
      resolver.current = resolve;
    });
  };

  const handleOk = () => {
    resolver.current(true);
    setIsOpen(false);
  };

  const handleCancel = () => {
    resolver.current(false);
    setIsOpen(false);
  };

  const value = {
    showConfirmation: handleShowDialog,
    setConfirmForm,
  };

  return (
    <ConfirmationContext.Provider value={value}>
      {children}
      <ConfirmDialog
        isOpen={isOpen}
        title={title}
        content={content}
        onConfirm={handleOk}
        onCancel={handleCancel}
      />
    </ConfirmationContext.Provider>
  );
};

const { Consumer: ConfirmationConsumer } = ConfirmationContext;

export { ConfirmationProvider, ConfirmationConsumer };

export default ConfirmationContext;

이러한 confirm 창이 나오도록 하는 DeleteButton 컴포넌트는 다음과 같다.

import React, { useContext } from 'react';

import Button from '@material-ui/core/Button';

import ConfirmationContext from '../../contexts/ConfirmationContext';

const DeleteButton = ({ onClickDelete }) => {
  const { showConfirmation, setConfirmForm } = useContext(ConfirmationContext);

  const handleOnClick = async () => {
    setConfirmForm({
      title: '삭제하시겠습니까?',
      content: '삭제하면 되돌릴 수 없습니다.',
    });

    const result = await showConfirmation();

    if (result) {
      onClickDelete();
    }
  };

  return (
    <Button
      size="small"
      variant="contained"
      color="secondary"
      onClick={handleOnClick}
    >
      Delete
    </Button>
  );
};

export default DeleteButton;

DeleteButton에서는 ContextshowConfirmation 함수를 이용하여, confirm창을 열어준다.
여기서 YES 버튼을 누르면 handleOk 함수가 수행되면서 Promise.resolve(true)를 반환하도록 하였다.

이에 DeleteButton에 props로 보내준 onClickDelete가 동작하게 된다.

이를 사용하는데 문제가 없었지만, 문제는 Context 자체에 대한 테스트를 어떻게 해야 할지 몰랐다.

이에 대해 윤석님이 큰 도움을 주셨다. #63

import React, { useContext } from 'react';

import {
  render,
  fireEvent,
  waitForElementToBeRemoved,
} from '@testing-library/react';

import ConfirmationContext, {
  ConfirmationProvider,
} from './ConfirmationContext';

describe('ConfirmationContext', () => {
  const title = '제목';
  const content = '내용';
  const open = '열기';

  function TestComponent() {
    const modal = useContext(ConfirmationContext);

    async function handleClick() {
      modal.setConfirmForm({ title, content });

      await modal.showConfirmation();
    }

    return (
      <button type="button" onClick={handleClick}>{open}</button>
    );
  }

  const renderTestComponent = () => render((
    <ConfirmationProvider>
      <TestComponent />
    </ConfirmationProvider>
  ));

  describe('Open modal', () => {
    it('shows title and content', () => {
      const { getByText, queryByText } = renderTestComponent();

      fireEvent.click(getByText(open));

      expect(queryByText(title)).toBeInTheDocument();
      expect(queryByText(content)).toBeInTheDocument();
    });
  });

  describe('Click cancel button', () => {
    it('close the modal', async () => {
      const { getByText, queryByText } = renderTestComponent();

      fireEvent.click(getByText(open));

      fireEvent.click(getByText('NO'));

      await waitForElementToBeRemoved(getByText(title));

      expect(queryByText(title)).not.toBeInTheDocument();
    });
  });

  describe('Click submit button', () => {
    it('close the modal', async () => {
      const { getByText, queryByText } = renderTestComponent();

      fireEvent.click(getByText(open));

      fireEvent.click(getByText('YES'));

      await waitForElementToBeRemoved(getByText(title));

      expect(queryByText(title)).not.toBeInTheDocument();
    });
  });
});

먼저 이 ConfirmationContext를 어떻게 사용하는지 생각해 본다. Context는 'Delete' 버튼이 눌렸을 때 Confirm창을 띄우므로 <DeleteButton /> 컴포넌트에서 사용된다.

따라서 <DeleteButton /> 컴포넌트에서 useContext hook을 이용하여 Context의 value값을 사용한다.

<DeleteButton />과 비슷한 동작을 하는 컴포넌트를 TestComponent로 만든 뒤 이를 <ConfirmationProvider>로 감싼다.

그 다음 confirm창을 열 때, YES 버튼 클릭, NO 버튼 클릭 에 대해서 테스트를 수행한다.

여기서 몇가지 꿀팁을 얻었다.

screen.debug

react testing library에서 screen.debug 기능을 이용하면 테스트 도중 디버깅을 할 수 있다. 현재 테스트중인 DOM의 형태를 console에서 이쁘게 확인도 가능하다.

prettyDom

prettyDom을 이용하면 현재 render 한 container나 dom의 형태를 이쁘게 확인할 수 있다.
맨날 expect(container).toBe('') 이런식으로 확인하면서 굉장히 답답했는데 좋은 정보였다.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions