#2: Context API(TodoProvider) Test 해보기

Song-Minhyung·2023년 5월 18일
0
post-thumbnail

목차

  1. useState를 사용해 ToDo-List TDD 적용해보기
  2. Context API(TodoProvider) Test 해보기 👈 NOW
  3. useState로 관리하던 상태를 ContextAPI로 변경 + 기능추가
  4. ToDo App에 삭제, 토글, 수정 기능 추가하기
  5. Context API를 Recoil로 변경하기 (recoil test)

🏁 이전에 구현한 내용, 이번에 구현할 내용

TodoItem, TodoList, TodoApp, TodoForm 을 테스트 코드 작성 -> 코드 작성 순으로 진행했다.
이번에는 저번에 구현했던 내용을 Context api로 바꿀것이다.

왜냐하면 지금 TodoItem을 핸들링 해주는 함수가 TodoApp -> TodoList -> TodoItem으로 전달되고
TodoList는 쓰지도 않는 props를 전달하기 위해 받기만 하고 있기 때문이다.
지금은 그저 한개라고 생각 할수도 있지만 컴포넌트들이 많아진다면 이 깊이는 더 깊어질 것이다.

그래서 일단 context를 구현하고, provider를 테스트 해보려 한다.

🫥 TodoProvider 구현

TodoContext는 TodoList를 저장한다.
그리고 addTodo, deleteTodo 함수를 구현해야 한다.
아래는 위 내용을 구현한 코드이다.

//TodoContext.tsx
import { createContext, ReactNode, useRef, useState } from "react";

export interface Todo {
  id: number;
  value: string;
}
export interface TodoContextState {
  Todos: Todo[],
  addTodo: (value: string) => void;
  deleteTodo: (id: number) => void;
}

const TodoContext = createContext<TodoContextState | null>(null);

const TodoProvider = ({children, value, init = []}: {children: ReactNode, value?: TodoContextState, init?: Todo[]}) => {
  const [Todos, setState] = useState<Todo[]>(init ?? value?.Todos);
  const nextId = useRef(0);

  const addTodo = (value: string) => {
    setState(prev => [...prev, {id: nextId.current++, value}]);
  }

  const deleteTodo = (id: number) => {
    setState(prev => prev.filter(todo => todo.id !== id));
  }
  
  return (
    <TodoContext.Provider value={value ?? {Todos, addTodo, deleteTodo}}>
      {children}
    </TodoContext.Provider>
  );
}

export {TodoContext, TodoProvider};

위에서 TodoProvider가 init과 value를 가져오는 이유는 테스트 할 때 목업을 넣어주기 위함이다.

☢️ TodoProvider 테스트

이제 위의 provider를 테스트 해 줄 차례인데 그러기 위해선 test component를 만들어야 한다.

const TestComponent = () => {
    const {Todos, addTodo, deleteTodo} = useTodoContext();

    return (
      <>
        <button 
          data-testid="addTodo"
          onClick={() => addTodo(newTodoValue)}
        > add Todo Button </button>
        
        {Todos.map(todo => (
          <div key={todo.id}>
            {todo.value}
            <div 
              data-testid="deleteTodo"
              onClick={() => deleteTodo(todo.id)}
            > delete Todo Button</div>
        </div>
        ))}
      </>
    );
  }

TestComponent는 실제로 TodoProvider를 사용해서 값이 제대로 들어가는지, 제거되는지만 테스트 하면 된다.
그러므로 최소한의 모양으로 TestComponent를 작성한다.

참고로 useTodoContext는 provider 안에 있다면 context를 리턴해주고
만약 provider 밖에 있다면 에러를 발생시켜주는 훅이다.

//useTodoContext.tsx
import { useContext } from "react"
import { TodoContext } from "../context/TodoContext"

const useTodoContext = () => {
  const state = useContext(TodoContext);
  if (!state) {
    throw new Error('cannot find TodoContext Provider');
  }
  return state;
}

export default useTodoContext;

이제 TestComponent를 만들었으니
이 컴포넌트를 render 해서 테스트를 해주기만 하면 된다.

원래 TDD는 테스트 코드 작성 -> 코드 작성을 해야하는데
아직 어떻게 해야할지 감이 오질 않아서 반대로 작성했다.

그런데 이렇게 작성하면서 앞으로 context api 와 같은
상태관리 라이브러리의 테스트 코드 작성을 어떤식으로 해야할지 감이 왔다!

//TodoContext.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import {  TodoProvider } from "./TodoContext";
import { mockTodos } from "../hooks/useMockTodoContext";
import useTodoContext from "../hooks/useTodoContext";

const newTodoValue = "add new todo";

describe("TodoProvider Test", () => {
  
  const TestComponent = () => {
    const {Todos, addTodo, deleteTodo} = useTodoContext();

    return (
      <>
        <button 
          data-testid="addTodo"
          onClick={() => addTodo(newTodoValue)}
        > add Todo Button </button>
        
        {Todos.map(todo => (
          <div key={todo.id}>
            {todo.value}
            <div 
              data-testid="deleteTodo"
              onClick={() => deleteTodo(todo.id)}
            > delete Todo Button</div>
        </div>
        ))}
      </>
    );
  }

  const setup = () => {
    render(<TodoProvider init={mockTodos}><TestComponent/></TodoProvider>);
  }

  it("addTodo 확인, newTodoValue 추가 되는지", async () => {
    setup();

    const addButton = screen.getByTestId("addTodo")
    await userEvent.click(addButton);
    expect(screen.getByText(newTodoValue)).toBeInTheDocument();
  });

  it("deleteTodo 확인, 리스트중 첫번째 todo 지워지는지", async () => {
    setup();

    const deleteButton = screen.getAllByTestId("deleteTodo")[0];
    await userEvent.click(deleteButton);
    expect(screen.queryByText(mockTodos[0].value)).not.toBeInTheDocument();
  });
});

이렇게 테스트를 통해 context api의 함수들은 제대로된 작동이 보장되었으므로
앞으로 다른 컴포넌트에서 해당 함수들을 사용할 때는 함수가 제대로 호출되는지,
값은 제대로 들어가는지만 테스트 해주면 될것같다!

profile
기록하는 블로그

0개의 댓글

관련 채용 정보