📌 Mock Fn(모의함수)란?

테스트를 위한 함수로, 원하는 함수의 로직을 테스트 하려면 작성해야할 코드가 많지만 테스트 함수를 사용하여 원하는 테스트를 시험할 코드만 간략하게 작성하여 테스트해볼 수 있어 편리하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
test("`onToggle` 속성(prop)에 연결된 함수가 정상적으로 실행됩니다.", () => {
let expected = "triggering toggle event";
let received = "";

render(
<ToggleButton
onToggle={() => {
received = expected;
}}
/>
);

expect(received).not.toBe(expected);

const element = screen.queryByRole("button");

fireEvent.click(element); // click button element

expect(received).toBe(expected);
});
  • 위 예제는 버튼이 클릭되었을 때, 해당 문구가 할당이 제대로 되는지 확인하는 테스트 코드이다.

테스트 목적은 버튼 클릭이 제대로 인식되는지, 선택된 요소가 예상된 값으로 할당이 되는지를 확인한다.

이렇게 테스트 코드를 작성하는 것은 번거로운 일이다.

그러므로 간략하게 테스트 함수를 생성하여 이벤트가 발생할 때, 모의 함수가 호출되면 앞서 말한 로직을 구현해줄 수 있으므로 버튼이 클릭되었을 때, 모의 함수 호출이 제대로 되는지만 확인하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
// mock Fn
test("`onToggle` 속성(prop)에 연결된 함수가 정상적으로 실행됩니다.", () => {
const mockFn = jest.fn();

render(<ToggleButton onToggle={mockFn} />);

const element = screen.getByRole("button");

fireEvent.click(element); // click button element

expect(mockFn).toHaveBeenCalled();
});

✏️ React VirtualDOM Test

리액트에서는 VitualDOM에 렌더링을 하여 RealDOM과 비교하기 때문에 Test시에도 RealDOM이 아닌 VirtualDOM에 렌더링된 것으로 테스트를 해줘야한다.

1
2
3
4
5
6
7
test("컴포넌트는 기본적으로 <span> 요소로 렌더링 됩니다.", () => {
render(<A11yHidden data-testid="tester" />);

const element = screen.getByTestId("tester");

expect(element.localName).toBe("span");
});
  • RealDOM에서 querySelector()로 요소를 가져오는 것처럼 VirtualDOM에서는 screen으로 요소를 가져올 수 있다.
  • 해당 요소를 가져오기 위해서 data-testId 값을 사용하였다.

❗️ localName vs nodeName 차이

  • localName : 소문자를 반환
  • nodeName : 대문자를 반환

defaultProps 사용하기

리액트 컴포넌트에서는 props에 default 값을 주기 위해서 defaultProps를 사용한다.

직접 파라미터에 App({ as: ComponentName = 'div' }) 이런식으로 주는 것을 안티패턴으로 본다.

1
2
3
4
5
6
7
8
9
10
11
import styles from "./A11yHidden.module.css";

export function A11yHidden({ as: ComponentName, ...restProps }) {
return (
<ComponentName className={styles.container} {...restProps}></ComponentName>
);
}

A11yHidden.defaultProps = {
as: "span",
};
  • as 라는 props를 받으면 해당하는 ComponentName으로 태그를 감싸서 컴포넌트를 생성한다.

component는 PascalCase 명명 규칙을 따르기 때문에 이를 따르지 않으면 에러를 발생시킨다.

getByRole vs getByTestId

기본적으로 div, span 같은 태그는 Role(역할)이 없다. 테스트 케이스에서 이러한 요소를 가져오기 위해서는 Role이 없으니 다른 방법을 찾아야한다.

그에 대한 대안으로 테스트용 data-testId="tester" 값을 주고 getByTestId()를 사용하여 요소를 가져올 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// div 요소 가져올 경우
test('컴포넌트의 `as` 속성(prop) 값이 "div"인 경우, <div> 요소로 렌더링 됩니다.', () => {
render(<Banner as="div" data-testid="tester" />);

const element = screen.getByTestId("tester");
expect(element.localName).toBe("div");
});

// figure 요소 가져올 경우
test("컴포넌트는 기본적으로 <figure> 요소로 렌더링 됩니다.", () => {
render(<Banner />);

const element = screen.getByRole("figure");
expect(element.localName).toBe("figure");
});

figure, img 등 우리가 직접 Role을 선정해주지 않아도 브라우저에서 해당 태그들을 해석할 때 Role을 할당해주기 때문에 이러한 요소들은 getByRole()를 이용하여 가져올 수 있다.

🏓 소감

마침 우테코에서도 Jest를 사용하고 있었는데, 수업시간 때 리액트 DOM 테스트를 해볼 수 있는 기회가 있어서 유익했다. 프론트 엔드에서는 사용자의 행동에 의해 UI가 변경되는 요소들이 많기 때문에 이러한 사용자 액션에 의한 test를 하기 어렵다.

이럴 경우 사용자가 행동하는 것처럼 테스트를 진행하는 storyBook이라는 testing Tool도 있으니 Jest가 익숙해지면 storyBook도 사용해봐야겠다.