Jest를 사용해보자! - 3편
2023. 10. 30. 16:28ㆍReact
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 ' 명령어를 치면 에러를 하나를 만날 수 있을것이다.
- 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차 에러
- 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 이상 버전에서 지원한다.
- 위의 차이점을 보면서 내가 찾아왔던 블로그 글들은 대부분 옛날 글들이란 사실을 알았다..... 그래서 위의 테스트 코드들도 옛날 버전일 가능성이 매우매우 높다. 다시 공식문서를 찾아보면서 해야하나라는 생각이 든다.
'React' 카테고리의 다른 글
Jest를 사용해보자! - 4편, Jest에서 절대경로 설정하기. (1) | 2023.11.22 |
---|---|
Jest를 사용해보자! - 2편 (1) | 2023.10.27 |
Jest를 사용해보자! - 1편 (0) | 2023.10.26 |
웹 캘린더 포트폴리오 만들면서 (1) | 2023.10.11 |
'string | null' 형식은 'string' 형식에 할당할 수 없습니다. 'null' 형식은 'string' 형식에 할당할 수 없습니다.ts(2322) (0) | 2023.08.21 |