[react]2️⃣ TDD 방법으로 todolist-TodoForm

Grace·2021년 5월 4일
4

react-testing-library

목록 보기
2/5
post-thumbnail

TDD 개발 흐름으로 todolist 만들어보기

이번 작업에서는 TDD 개발 흐름에 따라서, 브라우저 확인은 마지막에 해볼 예정

1. 새 프로젝트 환경 만들기

새 프로젝트를 생성하고

$ npx create-react-app rtl-tdd-todos

해당 디렉터리에서 필요한 라이브러리들을 설치 후

$ yarn add @testing-library/react @testing-library/jest-dom @types/jest

생성된 setupTest.js에 import 해준다.
(9.07 수정)

import "@testing-library/react/cleanup-after-each";
import "@testing-library/jest-dom/extend-expect";

첫번째는 각 테스트가 DOM에 렌더링해놓은 내용들을
테스트가 끝날 때 다음 테스트를 위해서 지워주는 것
이고,
두 번째는 jest-dom가 제공하는 matcher를 Jest 테스트 러너에게 인식시키는 것이다.

2. 컴포넌트 생성 계획 세우기

먼저 Todolist를 만들기 위해 어떤 컴포넌트와 기능들이 필요한지 계획을 세운다.
TDD개발 흐름으로 만든다는게, 유닛테스트들을 거쳐서
마지막에 통합테스트로 최종점검(?) 하는 흐름인지는 잘 모르겠지만 😓
작은(자식) 컴포넌트 -> 큰(부모) 컴포넌트 순으로 작업을 할 예정이다.

  • TodoForm
    inputbutton으로 이루어진 form, 거기서 submit 이벤트가 발생하면 새로운 항목이 추가되어야 함
  • TodoItem
    list에 나열될 todo 항목을 보여주는 컴포넌트. todo객체를 props로 받아서 렌더링 해줌
    텍스트를 클릭하면 삭제선이 토글되고, 우측 삭제버튼을 누르면 항목이 사라지게 하기
  • TodoItemList
    todo 배열을 받아와 여러개의 TodoItem 컴포넌트로 렌더링
  • TodoApp
    할일목록 추가, 토글, 삭제 기능이 구현되는 컴포넌트

3. TDD 개발 시작

전체적인 작업은 컴포넌트 틀 생성 -> 테스트코드 작성 -> 코드 통과시키기 -> 리팩토링 순으로 진행된다.

TodoForm 만들기

UI 구성하기

src 디렉터리에 TodoForm.js 빈 틀을 작성한 후 테스트코드로 UI를 구성해준다.

TodoForm.test.js

import React from "react";
import { render } from "@testing-library/react";
import TodoForm from "./TodoForm";

describe("<TodoForm />", () => {
 // input과 button의 유무를 확인
 it('has input and a button', () => {
   const { getByText, getByPlaceholderText } = render(<TodoForm />);
     getByPlaceholderText('할 일을 입력하세요');
     getByText('등록');
 });
});

이렇게 작성된 테스트코드를 확인하기 위해서는
$ yarn test를 입력하면 terminal에 결과창이 뜨게 된다.
예시로, 이전에 캡쳐해둔 사진 사용하기 ㅎ
테스트 케이스가 통과하게 되면

실패하게 되면

현재로서는 위의 코드만 작성하면 당연히 테스트케이스가 실패로 나온다.
통과 시키기 위해서 TodoForm.js를 수정해준다.

TodoForm.js

import React from 'react';

const TodoForm = () => {
  return (
    <form>
      <input placeholder="할 일을 입력하세요" />
      <button type="submit">등록</button>
    </form>
  );
};

export default TodoForm;

이렇게 코드 작성후 저장을 하면 테스트케이스가 통과로 뜨게 된다.

Input 상태 관리하기

fireEvent를 사용하여 input에 change 이벤트를 발생시키면 value 값이 바뀌게 만든다.

TodoForm.test.js

...

it('changes input', () => {
    const { getByPlaceholderText } = render(<TodoForm />);
    const input = getByPlaceholderText('할 일을 입력하세요');
    fireEvent.change(input, {
      target: {
        value: 'TDD 배우기'
      }
    });
    // toHaveAttribute는 해당 DOM에 특정 속성이 있는지 확인해줌
    expect(input).toHaveAttribute('value', 'TDD 배우기'); 
  });

실패한 테스트코드를 성공시키기 위해서 input 상태에 대한 코드를 추가해준다.

TodoForm.js

import React, { useState, useCallback } from 'react';

const TodoForm = () => {
  const [value, setValue] = useState('');
  const onChange = useCallback(e => {
    setValue(e.target.value);
  }, []);

  return (
    <form>
      <input
        placeholder="할 일을 입력하세요"
        value={value}
        onChange={onChange}
      />
      <button type="submit">등록</button>
    </form>
  );
};

export default TodoForm;

submit 이벤트 관리하기

input에 대한 상태관리가 마무리 되었으니 이번에는 button클릭시에 발생하는
submit 이벤트를 관리할 차례.
TodoForm 컴포넌트는 onInsert라는 함수를 props로 받아오면서
submit 이벤트가 발생하면 호출될 것이다. 이 때 input 창은 비워져야 한다.

TodoForm.test.js

...

it('calls onInsert and clears input', () => {
    const onInsert = jest.fn(); // mock함수 - 호출된 것을 쉽게 확인할 수 있는 함수
    const { getByText, getByPlaceholderText } = render(
      <TodoForm onInsert={onInsert} />
    );
    const input = getByPlaceholderText('할 일을 입력하세요');
    const button = getByText('등록');
    // 수정하고
    fireEvent.change(input, {
      target: {
        value: 'TDD 배우기'
      }
    });
    // 버튼 클릭
    fireEvent.click(button);
    expect(onInsert).toBeCalledWith('TDD 배우기'); // onInsert 가 'TDD 배우기' 파라미터가 호출됐어야함
    expect(input).toHaveAttribute('value', ''); // input이 비워져야함
  });

위의 테스트 코드를 통과시키기 위해 button 클릭을 위한 코드작업을 해준다.

TodoForm.js

import React, { useState, useCallback } from 'react';

const TodoForm = ({ onInsert }) => {
  const [value, setValue] = useState('');
  const onChange = useCallback(e => {
    setValue(e.target.value);
  }, []);
  const onSubmit = useCallback(
    e => {
      onInsert(value);
      setValue(''); // input창 비우기
      e.preventDefault(); // 새로고침을 방지함
    },
    [onInsert, value]
  );

  return (
    <form onSubmit={onSubmit}>
      <input
        placeholder="할 일을 입력하세요"
        value={value}
        onChange={onChange}
      />
      <button type="submit">등록</button>
    </form>
  );
};

export default TodoForm;

이렇게 작업해주면 테스트코드에 pass가 뜨게 된다.
작성된 코드들을 확인해보면, 테스트코드쪽에서 반복되는 코드가 있기에 리팩토링을 해준다.

getByPlaceholderText('할 일을 입력하세요'); 
getByText('등록'); 

↓ ↓ ↓ ↓ ↓
TodoForm.test.js

import React from 'react';
import { render, fireEvent } from 'react-testing-library';
import TodoForm from './TodoForm';

describe('<TodoForm />', () => {
  const setup = (props = {}) => {
    const utils = render(<TodoForm {...props} />);
    const { getByText, getByPlaceholderText } = utils;
    const input = getByPlaceholderText('할 일을 입력하세요'); 
    const button = getByText('등록'); 
    return {
      ...utils,
      input,
      button
    };
  };

  it('has input and a button', () => {
    const { input, button } = setup();
    expect(input).toBeTruthy(); // 해당 값이 truthy 한 값인지 확인
    expect(button).toBeTruthy();
  });

  it('changes input', () => {
    const { input } = setup();
    fireEvent.change(input, {
      target: {
        value: 'TDD 배우기'
      }
    });
    expect(input).toHaveAttribute('value', 'TDD 배우기');
  });

  it('calls onInsert and clears input', () => {
    const onInsert = jest.fn();
    const { input, button } = setup({ onInsert }); // props 가 필요 할땐 이렇게 직접 파라미터로 전달
    // 수정하고
    fireEvent.change(input, {
      target: {
        value: 'TDD 배우기'
      }
    });
    // 버튼 클릭
    fireEvent.click(button);
    expect(onInsert).toBeCalledWith('TDD 배우기'); 
    expect(input).toHaveAttribute('value', ''); 
});

setup이란 함수를 새로 만들어서 그 안에서 inputbutton을 미리 찾는 작업을 하고,
render 결과물 안에 들어있는 함수, 객체, input, button을 함께 반환해준다.
그리고 setup함수에선 props를 파라미터로 받아오며, 필요시에 props를 넣을 수 있게 해준다.

이 작업까지 마치면 깔끔해진 코드들을 볼 수 있다 :)

다음 포스팅에서 TodoItem 작업을 이어가보자 -

profile
쉽게 사는건 재미가 없더군요, 새로 시작합니다🤓

0개의 댓글