npm test
또는 npm test a
Create React App
으로
컴포넌트 제작 (src/component/Hello.js
)
export default function Hello() {
return <h1>Hello!</h1>;
}
컴포넌트 import (src/App.js
)
Hello
컴포넌트 importimport "./App.css";
import Hello from "./component/Hello"; // 👈 Hello 컴포넌트 import
function App() {
return (
<div className="App">
<Hello /> {/* 👈 불필요한 코드 제거, Hello 사용 */}
</div>
);
}
export default App;
화면
잘 보임 (Hello)
user 제작 후, 전달 (src/App.js
)
import "./App.css";
import Hello from "./component/Hello";
const user = { // 👈 user 제작
name: "Mike",
age: 30,
};
function App() {
return (
<div className="App">
<Hello user={user} /> {/* 👈 user 전달 */}
</div>
);
}
export default App;
name 따라 조건부 렌더링 (src/component/Hello.js
)
/*
user를 받아서
- user의 이름이 있으면 -> Hello 뒤에 이름을 보여줌
- 없으면 Login 버튼을 보여줌 */
export default function Hello({ user }) {
return user.name ? <h1>Hello! {user.name}</h1> : <button>Login</button>;
}
화면
Hello! 이름
잘 보임 Create React App
의 테스트는 Jest를 사용 → 별도 설치 없이 바로 테스트 코드를 작성package.json
에 이미 test 명령어 존재 결과
실패
실패 원인 확인 (./src/App.test.js
)
이 파일을 지워야 함.
// 기존에 작성되어 있었던 테스트
import { render, screen } from '@testing-library/react'; // 👈
import App from './App';
test('renders learn react link', () => { // 👈 실패 원인
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
/*
learn react link 가 렌더링 되고 있는지 보고 있는데 이미 지웠음.
실패하는 것이 맞음. */
src/component/Hello.test.js
)파일명: 동일한 이름에 test
를 붙임.
import { render, screen } from "@testing-library/react"; // 👈 @testing-library/react에 있는 render, screen 이용
import Hello from "./Hello";
const user = {
name: "Mike",
age: 30,
};
test("Hello라는 글자가 포함되어 있는가?", () => {
render(<Hello user={user} />); {/* 👈 render를 통해서 Hello 컴포넌트를 불러오고 user를 props로 전달 */}
const helloEl = screen.getByText(/Hello/i); {/* 👈 screen에서 Hello라는 텍스트가 있는지 확인 */}
expect(helloEl).toBeInTheDocument(); {/* 👈 테스트 실행 (Document안에 방금 지정한 위 텍스트가 있는지 확인) */}
});
성공하는 케이스를 찍어두고 비교
실패하는 경우
렌더링된 화면과 찍어둔 화면이 다를 경우
toMatchSnapshot()
활용
name
이 있는 경우와 없는 경우
src/component/Hello.test.js
import { render, screen } from "@testing-library/react";
import Hello from "./Hello";
const user = { // 👈 이름 있는 user
name: "Mike",
age: 30,
};
const user2 = { // 👈 이름 없는 user
age: 20,
};
// 👇 이렇게 2가지 경우를 만들었고요.
test("snapshot : name 있음", () => { // 👈 이름 있는 경우
const view = render(<Hello user={user} />);
expect(view).toMatchSnapshot(); // 👈 toMatchSnapshot()
});
test("snapshot : name 없음", () => { // 👈 이름 없는 경우
const view = render(<Hello user={user2} />);
expect(view).toMatchSnapshot(); // 👈 toMatchSnapshot()
});
test("Hello라는 글자가 포함되어 있는가?", () => {
render(<Hello user={user} />);
const helloEl = screen.getByText(/Hello/i);
expect(helloEl).toBeInTheDocument();
});
2개의 스냅샷이 작성되었다라는 메세지 존재
src/component/__snapshots__/Hello.test.js.snap
이름이 없을 때 → 로그인 버튼 有
이름이 있을 때 → Hello! Mike
찍힘
user의 이름 수정 (src/component/Hello.test.js
)
(...생략)
const user = {
name: "Tom", // 👈 이름을 Mike에서 Tom으로 수정
age: 30,
};
(...생략)
테스트 결과
실패
이유
아까는 Mike
로 작성되어 있었기 때문
스냅샷에는 Mike
로 되어 있고 지금 전달한 이름은 Tom
방법
테스트 후, u 누르기
업데이트 하는 경우 & 주의
[업데이트 하는 경우] 예시
Hello.js
파일에 문제 無[주의] 신중하게 결정
만약 버그가 있어서 실패한 건데 업데이트 해버리면 이후부터는 버그가 있는 상태에서 테스트가 통과됨
업데이트 후
터미널
하나의 스냅샷이 업데이트가 됐고 모두 성공
Hello.test.js.snap
업데이트 사항 반영됨
Mike → Tom
[예시]
user
전달 하지 않을 경우
user 전달 안하도록 수정 (src/component/Hello.test.js
)
(...생략)
const user2 = {
age: 20,
};
(...생략)
test("snapshot : name 없음", () => {
const view = render(<Hello />); // 👈 user 전달 안함
expect(view).toMatchSnapshot();
});
(...생략)
통과 되야 함.
(로그인 버튼이 나와야 되니까. 아까 찍어둔 스냅샷은 로그인 버튼이 노출되는 화면이 있으니까.)
테스트 결과
실패
로그
- name을 찾을 수 없다.
- Hello 컴포넌트에서 에러 발생
버그 수정 (src/component/Hello.js
)
테스트 실패 원인
user
없음 → user.name
도 못 찾음 → 에러 발생
name
이 없든 user
자체가 없든 로그인 버튼을 보여주는 것이 맞음
export default function Hello({ user }) {
return user.name ? <h1>Hello! {user.name}</h1> : <button>Login</button>;
// 👆
}
버그 수정
user
의 여부도 체크
// [수정] user도 있고 user의 이름도 있고
export default function Hello({ user }) {
return user && user.name ? ( // 👈 user && 추가
<h1>Hello! {user.name}</h1>
) : (
<button>Login</button>
);
}
테스트 결과
통과
리팩토링 (src/component/Hello.js
)
옵셔널 체이닝
user
가 없어도 에러를 내지 않고 undefined
반환 → 믿고 사용 가능
좀 더 간단
export default function Hello({ user }) {
return user?.name ? <h1>Hello<! {user.name}</h1> : <button>Login</button>;
// 👆 ? 추가
}
테스트 결과
마찬가지로 통과
추천하는 경우
복잡한 디자인이 있을 때
코드를 일일이 대조하면서 확인하는 작업 대체
색깔이나, 리스트의 갯수나 텍스트들을 비교
한 줄, 한 줄 테스트 코드를 짜는 것 대체
비추천 하는 경우
불필요한 테스트가 많아질 수도 있을 때
기획에 따라 UI가 계속 바뀌는 경우
배경 이미지나 아이콘, 이벤트 문구 등이 자주 바뀌는 환경이라면 테스트는 계속 실패하게 될 것이고 매번 업데이트 해주는 것이 작업의 전부가 될 것
시간에 따라서 변할 수 있는 값들은 테스트 전에
Mock Function
를 이용해서 고정된 값으로 바꾸기
src/component/Timer.js
Timer
컴포넌트 제작
현재의 초를 보여줌
새로 고침 할 때 마다 초가 바뀜. (진입 시점의 초를 보여주기 때문)
export default function Timer() {
const now = Date.now();
const sec = new Date(now).getSeconds();
return <p>현재 {sec}초 입니다.</p>;
}
src/App.js
import "./App.css";
import Timer from "./component/Timer"; // 👈
function App() {
return (
<div className="App">
<Timer /> {/* 👈 */}
</div>
);
}
export default App;
src/component/Timer.test.js
import { render } from "@testing-library/react";
import Timer from "./Timer";
test("초 표시", () => {
const view = render(<Timer />);
expect(view).toMatchSnapshot();
});
터미널 결과
첫 테스트기 때문에 스냅샷 파일이 형성됨.
src/component/__snapshots__/Timer.test.js.snap
34초라고 표시 됨
대부분의 확률로 실패
56초라고 나오고 다시 테스트하면 1초라고 나옴.
매번 업데이트(u
) 해도 계속 실패할 것이 뻔함.
→ 해결: mock Function
사용
src/component/Timer.test.js
목 함수 제작
import { render } from "@testing-library/react";
import Timer from "./Timer";
test("초 표시", () => {
Date.now = jest.fn(); // 👈 목 함수 제작
const view = render(<Timer />);
expect(view).toMatchSnapshot();
});
숫자 반환하는 함수로 작성
(...생략)
test("초 표시", () => {
Date.now = jest.fn(() => 12345678); // 👈 숫자 하나 반환
(...생략)
});
이제 Date.now
는 123456789
으로 고정
테스트
당연히 실패
u
를 눌러서 업데이트
확인
src/component/__snapshots__/Timer.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`초 표시 1`] = `
Object {
"asFragment": [Function],
"baseElement": <body>
<div>
<p>
현재
45 👈 45초로 바뀜
초 입니다.
</p>
</div>
</body>,
(...생략)
계속 통과
통과 이유
테스트는 언제나 src/component/Timer.test.js
의 12345678
이 숫자로 수행이 되기 때문
참고