Jest를 사용해보자! - 3편

2023. 10. 30. 16:28React

1. 3편은 버튼 클릭 시, Count 올라가는 걸 만들거야.

  • 1편은 셋팅, 2편은 로그인 폼까지만 만드는 걸 했다.
  • 이번엔 button을 클릭하면 숫자가 1씩 증가하거나 감소하는 컴포넌트를 만들고 테스트를 해보려고 한다.

 

2. 기본적인 세팅을 해보자.

  • App.test.tsx 파일이 아닌 Counting.test.tsx 파일을 만들어주고, src 밑에도 Counting.tsx란 파일을 생성해주고 코드를 작성해주었다.
// Counting.tsx

import { useState } from 'react';

export function Counting() {
  const [count, setCount] = useState<number>(0);
  
  const onIncrease = () => {
    setCount((prev) => prev + 1);
  }

  const onDecrease = () => {
    setCount((prev) => prev - 1);
  }

  return(
    <>
      <span>{count}</span>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </>
  )
}

 

// Counting.test.tsx

import '@testing-library/jest-dom';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';
import { Counting } from "../src/Counting";

describe("Click the Button for counting", () => {
  let container: HTMLDivElement;

  beforeEach(() => {
    container = document.createElement('div');
    document.body.appendChild(container);
    act(() => {
      ReactDOM.createRoot(container).render(<Counting />);
    })
  })

  afterEach(() => {
    document.body.removeChild(container);
    container.remove();
  })

  it("Renders all button fileds correctly", () => {
    const buttons = container.querySelectorAll("button");
    expect(buttons).toHaveLength(2);
    expect(buttons[0].type).toBe("button");
    expect(buttons[1].type).toBe("button");
  })
})

 

3. 내가 만난 에러들.

1) 1차 에러

  • 위의 코드를 작성한 후에 ' yarn test ' 명령어를 치면 에러를 하나를 만날 수 있을것이다.

Click the Button for counting 부분에서 button 타입이 submit 이래!

  • Counting.tsx 에 button에 type을 따로 설정해주지 않아 submit으로 인식했다는 것이다. 호다닥 고쳐주자.
// Counting.tsx
return(
    <>
      <span>{count}</span>
      <button type="button" onClick={onIncrease}>+1</button>
      <button type="button" onClick={onDecrease}>-1</button>
    </>
)

 

2) 2차 에러

  • 이렇게 고친 후에 ' yarn test ' 명령어를 치면.

어딜! 선언만 해놓고!!!

  • React를 선언해두고 사용하지 않았으니 지워달란다. 그래서 지우면 typescript가 
'React'은(는) UMD 전역을 참조하지만 현재 파일은 모듈입니다. 대신 가져오기를 추가해 보세요. ts(2686)
  • 이런 에러 문구를 던져준다. 여러 방법이 있었는데 안전하게 type도 선언해줄 수 있는 방법을 택했다.
// Counting.test.tsx
...
    act(() => {
      ReactDOM.createRoot(container).render(<Counting /> as React.JSX.Element);
    })
...
  • Counting 컴포넌트가 React의 JSX element 니까 as로 설정해주면 깔끔하게 에러를 해결할 수 있다.

 

3) 3차 에러

Warning이 더 싫은 느낌이....

  • Counting.tsx 에서 현재 테스트 환경은 act를 지원하지 않는다고 한다. ?????? App.test.tsx 에서의 act와 동일한데 뭐가 다른걸까??
  • 해결 방법을 찾아보았다...
  • 위의 2개를 찾아봤는데 이게 무슨 소린가 싶었다. @testing-library/react 의 act 를 쓰지말고 react-dom/test-utils를 사용하라는데 난 후자를 사용중인데.... // In your test setup file 이 부분도 이해가 가지 않았다.
  • 일단은 후자를 쓰고 있으니 전자로 바꿔봤다.

응...? 왜 됨?

  • 테스트 코드 통과.... 뭐가 다른지는 하단에 남겨놓겠다.

 

4) 증가 버튼 클릭 시, 증가되는지 확인해보자.

  • 버튼이 제대로 렌더링 되는 걸 알았으니 이제 클릭했을 때 변화하는지를 구현해야 한다. 그 전에 count가 0부터 시작하는지 확인을 해보려고 한다.
// Counting.test.tsx

it("Count 0에서 시작?" , () => {
    const countElement = container.querySelectorAll("span");
    expect(countElement).toHaveLength(1);
    expect(countElement[0]).toHaveTextContent("0");
  })
  • 위의 코드를 추가해서 확인했고, 이제 증가 버튼을 누를 때 span의 텍스트가 1로 바뀌는지 확인해보려고 한다.
// Counting.test.tsx

it("+ 버튼 클릭 시 count +1로 변화?" , () => {
    const countElement = container.querySelector("span[data-testid='count-display']");
    const incrementButton = container.querySelector("button[data-testid='increment-button']");

    if(countElement && incrementButton){
      expect(countElement).toHaveTextContent("0");

      act(() => {
        fireEvent.click(incrementButton);
      });

      expect(countElement).toHaveTextContent("1");
    } else {
      console.error("엘리먼트를 찾을 수 없음.");
    }
    
});
  • 위의 코드를 test 에 추가해줬다. querySelectorAll을 사용하다보면 테스트하는 과정에서 헷갈릴 것 같아 button과 span에 data-testid 를 추가하여 구분해줬다. 그리고 if() 문을 사용한 이유는 countElement와 incrementButton이 null이 될 가능성이 있기 때문에 사전에 미리 element가 존재하는지를 묻기 위해 사용하였다.
  • 연속하여 클릭 시, 증가되는지를 확인하려면 여러번 click을 만들고 그 때 예상되는 값이 맞는지를 확인해주면 된다.
it("+ 버튼 클릭 시 count 3으로 변화?" , () => {
    const countElement = container.querySelector("span[data-testid='count-display']");
    const incrementButton = container.querySelector("button[data-testid='increment-button']");

    if(countElement && incrementButton){
      expect(countElement).toHaveTextContent("0");

      act(() => {
        fireEvent.click(incrementButton);
        fireEvent.click(incrementButton);
        fireEvent.click(incrementButton);
      });

      expect(countElement).toHaveTextContent("3");
    } else {
      console.error("엘리먼트를 찾을 수 없음.");
    }
 });

 

5) 감소 버튼 클릭 시, 감소되는지 확인해보자.

it("- 버튼 클릭 시 count -1로 변화?" , () => {
    const countElement = container.querySelector("span[data-testid='count-display']");
    const decrementButton = container.querySelector("button[data-testid='decrement-button']");

    if(countElement && decrementButton){
      expect(countElement).toHaveTextContent("0");

      act(() => {
        fireEvent.click(decrementButton);
      });

      expect(countElement).toHaveTextContent("-1");
    } else {
      console.error("엘리먼트를 찾을 수 없음.");
    }    
});
  • 증가 버튼과 동일하게 작성해주었다. data-testid 와 예상되는 값을 바꿔주면 된다.

 

6) 증가 버튼 눌렀다가 감소 버튼 누를땐?

  • 시작은 0, 증가 버튼을 누르면 1, 감소 버튼을 누르면 다시 0 으로 돌아오는지 확인해보자.
it("+ 버튼 눌렀다가 - 버튼 눌렀을 때는?" , () => {
    const countElement = container.querySelector("span[data-testid='count-display']");
    const incrementButton = container.querySelector("button[data-testid='increment-button']");
    const decrementButton = container.querySelector("button[data-testid='decrement-button']");

    if(countElement && incrementButton && decrementButton){
      expect(countElement).toHaveTextContent("0");

      act(() => {
        fireEvent.click(incrementButton);
      });

      expect(countElement).toHaveTextContent("1");

      act(() => {
        fireEvent.click(decrementButton);
      })

      expect(countElement).toHaveTextContent("0");
    } else {
      console.error("엘리먼트를 찾을 수 없음.");
    }
});

 

전체 코드

더보기
// Counting.tsx

import { useState } from 'react';

export function Counting() {
  const [count, setCount] = useState<number>(0);
  
  const onIncrease = () => {
    setCount((prev) => prev + 1);
  }

  const onDecrease = () => {
    setCount((prev) => prev - 1);
  }

  return(
    <>
      <span data-testid="count-display">{count}</span>
      <button data-testid="increment-button" type="button" onClick={onIncrease}>+1</button>
      <button data-testid="decrement-button" type="button" onClick={onDecrease}>-1</button>
    </>
  )
}

 

// Counting.test.tsx

import '@testing-library/jest-dom';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act, fireEvent } from '@testing-library/react';
import { Counting } from "../src/Counting";

describe("Click the Button for counting", () => {
  let container: HTMLDivElement;

  beforeEach(() => {
    container = document.createElement('div');
    document.body.appendChild(container);
    act(() => {
      ReactDOM.createRoot(container).render(<Counting /> as React.JSX.Element);
    })
  })

  afterEach(() => {
    document.body.removeChild(container);
    container.remove();
  })

  it("Count 0에서 시작?" , () => {
    const countElement = container.querySelectorAll("span");
    expect(countElement).toHaveLength(1);
    expect(countElement[0]).toHaveTextContent("0");
  })

  it("Renders all button fileds correctly", () => {
    const buttons = container.querySelectorAll("button");
    expect(buttons).toHaveLength(2);
    expect(buttons[0].type).toBe("button");
    expect(buttons[1].type).toBe("button");
  })

  it("+ 버튼 클릭 시 count +1로 변화?" , () => {
    const countElement = container.querySelector("span[data-testid='count-display']");
    const incrementButton = container.querySelector("button[data-testid='increment-button']");

    if(countElement && incrementButton){
      expect(countElement).toHaveTextContent("0");

      act(() => {
        fireEvent.click(incrementButton);
        fireEvent.click(incrementButton);
        fireEvent.click(incrementButton);
      });

      expect(countElement).toHaveTextContent("3");
    } else {
      console.error("엘리먼트를 찾을 수 없음.");
    }
    
  });

  it("- 버튼 클릭 시 count -1로 변화?" , () => {
    const countElement = container.querySelector("span[data-testid='count-display']");
    const decrementButton = container.querySelector("button[data-testid='decrement-button']");

    if(countElement && decrementButton){
      expect(countElement).toHaveTextContent("0");

      act(() => {
        fireEvent.click(decrementButton);
      });

      expect(countElement).toHaveTextContent("-1");
    } else {
      console.error("엘리먼트를 찾을 수 없음.");
    }
    
  });

  it("+ 버튼 눌렀다가 - 버튼 눌렀을 때는?" , () => {
    const countElement = container.querySelector("span[data-testid='count-display']");
    const incrementButton = container.querySelector("button[data-testid='increment-button']");
    const decrementButton = container.querySelector("button[data-testid='decrement-button']");

    if(countElement && incrementButton && decrementButton){
      expect(countElement).toHaveTextContent("0");

      act(() => {
        fireEvent.click(incrementButton);
      });

      expect(countElement).toHaveTextContent("1");

      act(() => {
        fireEvent.click(decrementButton);
      })

      expect(countElement).toHaveTextContent("0");
    } else {
      console.error("엘리먼트를 찾을 수 없음.");
    }
    
  });
})

 

 

4. 참고한 자료.

 

5. 정리

  • 낯설다. 그냥 어렵다는 느낌이다. 무작정 따라하기 느낌으로 여기저기 찾아보면서 따라하고 있는데... 다른 방법을 찾아봐야겠다. 버튼 클릭으로 진짜 대충 이렇게 쓰는거구나 라는 감은 잡았다. 근데 다른 코드 만들어보세요~ 하면 아직은 숨이 턱 막힌다. 
  • GPT한테 count 테스트 코드 짜달라했을 때 알려준 코드도 적어 두고 간다.
// Count.tsx

import React, { useState } from 'react';

function Count() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p data-testid="count-display">{count}</p>
      <button data-testid="increment-button" onClick={increment}>
        Increment
      </button>
    </div>
  );
}

export default Count;
// Count.test.tsx

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

test('Count component increments the count when the button is clicked', () => {
  const { getByTestId } = render(<Count />);
  const countDisplay = getByTestId('count-display');
  const incrementButton = getByTestId('increment-button');

  // 초기 카운트 값은 0
  expect(countDisplay.textContent).toBe('0');

  // 버튼 클릭 후 카운트 증가 확인
  fireEvent.click(incrementButton);
  expect(countDisplay.textContent).toBe('1');

  fireEvent.click(incrementButton);
  expect(countDisplay.textContent).toBe('2');
});
  • 엉엉 ㅠㅠ

1. ACT

  • @testing-library/react 의 act 와 react-dom/test-utils 의 act 의 차이점
    • react-dom/test-utils의 act는 React 16.3 이전 버전에서 사용하기에 유용하다. act 함수를 사용하여 비동기 작업 및 컴포넌트 업데이트를 동기화하는 것이 권장되어서 사용한 함수이다.
    • @testing-library/react의 act는 React 16.9.0 이상 버전에서 지원한다.
  • 위의 차이점을 보면서 내가 찾아왔던 블로그 글들은 대부분 옛날 글들이란 사실을 알았다..... 그래서 위의 테스트 코드들도 옛날 버전일 가능성이 매우매우 높다. 다시 공식문서를 찾아보면서 해야하나라는 생각이 든다.