#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개의 댓글