리액트-리덕스와 연결되어 있는 리액트 컴포넌트를 테스트해 보자.
먼저, 테스트 코드 작성에 필요한 라이브러리들을 설치해 준다.
npm install --save-dev @testing-library/react @testing-library/react-hooks redux-mock-store
기본적으로 통합 테스트를 작성하는 것이 좋지만, 단위 테스트를 작성해야 할 때도 있다.
단위 테스트는 이전 상태에 액션을 적용한 후, 새로운 상태를 반환하는 순수 함수
다. 대부분의 경우, 명시적인 테스트가 필요 없기 때문에 간단하게 테스트 코드를 작성할 수 있다. 특정 입력 state와 action으로 reducer를 호출하고, 결과가 일치하는지 확인하면 된다.
예시로, 다음과 같은 코드를 테스트하면,
drop: (state, actions) => {
if (state.stop) return;
if (state.winner !== null) {
console.warn(`이미 종료된 게임입니다. 승자는 ${state.winner}입니다.`);
return;
}
if (state.board[actions.payload.lineNumber][0] !== null) {
console.warn(
`${actions.payload.lineNumber + 1} 열은 이미 전부 채워진 열입니다.`
);
return;
}
let location: null | number = null;
/**
* 만약 선택한 행의 첫 번째 셀이 null이 아니면 리턴.
*/
for (let i = 6; i >= 0; i--) {
if (state.board[actions.payload.lineNumber][i] === null) {
state.board[actions.payload.lineNumber][i] =
state.currentPlayer === "RED" ? "RED" : "YELLOW";
location = i;
break;
}
}
if (location === 0) {
state.notMaxLine = state.notMaxLine.filter(
(lineNumber) => lineNumber !== actions.payload.lineNumber
);
}
state.markerCount += 1;
if (state.markerCount >= 7) {
// 모든 방향을 체크하기 위해 방향 벡터를 사용한다.
const movement = [
{ dx: 1, dy: 0 }, // 가로
{ dx: 0, dy: 1 }, // 세로
{ dx: -1, dy: 1 }, // 양수 대각선
{ dx: -1, dy: -1 }, // 음수 대각선
];
// 연결 테스트
const checkDirection = (dx: number, dy: number) => {
let count = 1; // 기존 마커도 추가.
let pnx = actions.payload.lineNumber + dx; // X축 각 방향별로 1칸씩 이동
let pny = location! + dy; // Y축 각 방향별로 1칸씩 이동
while (
// 각 좌표가 보드 내에 있고, 해당 위치에 있는 마커가 현재 유저의 마커와 같은 색상의 마커인지 확인한다.
// 만약 마커가 보드를 넘어갈 경우, 종료.
pnx >= 0 &&
pny >= 0 &&
pnx <= 6 &&
pny <= 5 &&
state.board[pnx][pny] === actions.payload.player
) {
count++;
pnx += dx;
pny += dy;
}
let mnx = actions.payload.lineNumber - dx; // X축 각 방향별로 1칸씩 이동
let mny = location! - dy; // Y축 각 방향별로 1칸씩 이동
while (
// 각 좌표가 보드 내에 있고, 해당 위치에 있는 마커가 현재 유저의 마커와 같은 색상의 마커인지 확인한다.
// 만약 마커가 보드를 넘어갈 경우, 종료.
mnx >= 0 &&
mny >= 0 &&
mnx <= 6 &&
mny <= 5 &&
state.board[mnx][mny] === actions.payload.player
) {
count++;
mnx -= dx;
mny -= dy;
}
return count;
};
for (const { dx, dy } of movement) {
const count = checkDirection(dx, dy);
if (count >= 4) {
state.winner = actions.payload.player;
if (state.winner === "RED") {
state.redWin += 1;
} else {
state.yellowWin += 1;
}
console.log(`${state.winner}가 승리했습니다.`);
return;
}
}
}
state.currentPlayer = actions.payload.player === "RED" ? "YELLOW" : "RED";
state.timer = 30;
},
다음과 같이 테스트 코드를 작성할 수 있다.
// src/tests/game.test.tsx
const previousState = {
board: [
[null, null, null, null, null, null],
[null, null, null, null, null, null],
[null, null, null, null, null, null],
[null, null, null, null, null, null],
[null, null, null, null, null, null],
[null, null, null, null, null, null],
[null, null, null, null, null, null],
],
currentPlayer: "RED" as "RED" | "YELLOW",
markerCount: 0,
winner: null,
redWin: 0,
yellowWin: 0,
timer: 30,
stop: false,
notMaxLine: [0, 1, 2, 3, 4, 5, 6],
};
gameSlice(previousState, drop({ lineNumber: 0 }))
// slice함수(기본값, reducer(payload))
reducer
가 있는 gameSlice
에 기본값과, 실행할 함수와 payload
를 전달해 준다.