Jest
자바스크립트 테스트 도구이다.
다음과 같이 설치하고
npm install --save-dev jest
npm script를 통해 실행하자.
{
"scripts": {
"test": "jest"
}
}
expect(테스트 데이터).toBe(결과 값)
제일 기본이 되는 문법이다.
expect의 파라미터에 테스트할 데이터, toBe에 결과값을 각각 입력한다.
1
2
3
4
5
6
7
8
9
10
|
const sum = require("./sum")
test("1+2는 3입니다.", () => {
expect(sum(1, 2)).toBe(3)
})
test("2+2는 4입니다.", () => {
expect(2 + 2).toBe(4)
})
|
.toEqual(주로 객체 데이터)
객체를 비교 할 때 사용된다.
1
2
3
4
5
|
test("객체를 비교합니다", () => {
const data = { one: 1 }
data["two"] = 2
expect(data).toEqual({ one: 1, two: 2 })
})
|
.not.
부정을 의미한다.
1
2
3
4
5
6
7
|
test("Not 문을 사용합니다", () => {
for (let a = 1; a < 10; a++) {
for (let b = 1; b < 10; b++) {
expect(a + b).not.toBe(0)
}
}
})
|
0과 Null 값에 대한 테스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
test("null에 대한 다양한 부정 테스트입니다.", () => {
const n = null
expect(n).toBeNull()
expect(n).toBeDefined()
expect(n).not.toBeUndefined()
expect(n).not.toBeTruthy()
expect(n).toBeFalsy()
})
test("0에 대한 다양한 부정 테스트입니다.", () => {
const z = 0
expect(z).not.toBeNull()
expect(z).toBeDefined()
expect(z).not.toBeUndefined()
expect(z).not.toBeTruthy()
expect(z).toBeFalsy()
})
|
숫자 비교 테스트
값이 이상인지 이하인지 같은지에 대한 비교를 할 수 있따.
1
2
3
4
5
6
7
8
9
10
11
|
test("숫자에 대한 테스트입니다.", () => {
const value = 2 + 2
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3.5)
expect(value).toBeLessThan(5)
expect(value).toBeLessThanOrEqual(4.5)
// toBe and toEqual are equivalent for numbers
expect(value).toBe(4)
expect(value).toEqual(4)
})
|
실수형 테스트
1
2
3
4
|
test("float 경우 toBe가 동작하지 않으므로 다음과 같이 사용합니다.", () => {
const value = 0.1 + 0.2
expect(value).toBeCloseTo(0.3)
})
|
.toMatch()
정규 표현식을 이용한 문자열 테스트를 한다.
1
2
3
4
5
6
7
8
|
test("정규표현식을 이용한 문자열 매칭입니다.1", () => {
expect("team").not.toMatch(/I/)
})
test("정규표현식을 이용한 문자열 매칭입니다.2", () => {
expect("Christoph").toMatch(/stop/)
})
|
.toContain()
배열안의 인자가 포함되어 있는지의 여부를 테스트 한다.
1
2
3
4
5
6
7
|
const shoppingList = ["diapers", "kleenex", "trash bags", "paper towels", "beer"]
test("배열에 contain 여부에 대한 테스트 입니다.", () => {
expect(shoppingList).toContain("beer")
expect(new Set(shoppingList)).toContain("beer")
})
|
.toThrow( (Error) )
에러를 테스트하는방법이다.
.toThrow의 에러 인자를 넣어 줄 경우 해당 에러인지 판별한다.
문자열을 넣어준다면 출력되는 문자열을 비교 할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function compileAndroidCode() {
throw new Error("you are using the wrong JDK")
}
test("예외에 대한 다양한 테스트 방법입니다.", () => {
//에러 발생을 판단.
expect(compileAndroidCode).toThrow()
expect(compileAndroidCode).toThrow(Error)
// 메세지를 정규표현식으로 판단.
expect(compileAndroidCode).toThrow("you are using the wrong JDK")
expect(compileAndroidCode).toThrow(/JDK/)
})
|
콜백 및 비동기 테스트
콜백 함수의 경우에
fetchData가 다 실행되기 전에 test가 끝난다.
done()을 이용하게 되면 test 안이 모두 수행될때까지 기다리고 마지막에 done을 통해 테스트가 종료되는 식으로 테스트를 할 수 있다.
비동기의 경우에는
그냥 async await을 통해 테스트 하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function fetchData(func) {
func("peanut butter")
}
test("the data is peanut butter", done => {
function callback(data) {
expect(data).toBe("peanut butter")
done() // fetchData가 동작하기 전에 test가 끝나버린다.
}
fetchData(callback)
})
//Promise
function fetchPromiseData() {
return new Promise((res, rej) => {
res("peanut butter")
})
}
test("the data is peanut butter", async () => {
const data = await fetchPromiseData()
expect(data).toBe("peanut butter")
})
|
라이프 사이클
테스트 전 후에 반복되는 코드를 세팅을 할 수 있다.
반복해서 사용하는 객체 등에 사용할 수 있다.
Each는 매번 테스트 마다
All 은 단 한번만 실행 된다.
콘솔이 어떻게 찍히는 지 확인 해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
beforeEach(() => {
console.log("Let's test")
})
afterEach(() => {
console.log("end Test")
})
beforeAll(() => {
return console.log("before only one")
})
afterAll(() => {
return console.log("after only one")
})
|
React with Jest
리액트 컴포넌트에 적용해보자.
프로젝트를 커스텀 해볼수도 있지만, 지금은 CRA를 이용하여 프로젝트를 생성하자.
create-react-app react-test
CRA로 설치하면 이미 test 코드가 준비되어 있다.
또한 react-scripts가 jest를 내장하고 있을 뿐만 아니라, hot-modules처럼 변화를 감지하고 계속 테스트를 해주는 끝내주는 모듈을 마련해두었다.
개인적으로 공부 할 때는 이것저것 이미 만들어져있는것을 하는 것을 좋아하진 않지만...
일단 Jest를 익히는게 우선이니까 다음에 커스텀 해보도록 하자.
npm run test를 하면
다음과 같은 메뉴가 떠야 정상이다.
어려운 영어는 아니니까 읽어보고 사용하면 좋을 거 같다.
a를 눌러 실행해보자.
컴포넌트 테스트 (Snapshot)
컴포넌트를 테스트 하기 위해서 예상되는 값에 컴포넌트를 입력하는 것은 힘들다.
이 때문에 Snapshot이라는 개념이 생겨 난거 같은데, 컴포넌트를 문자열 형태로 전환 하고 이를 __snapshot__파일에 저장한다. 만일 스냅샷에 컴포넌트가 이미 있다면 덮어 쓰지 않는다.
이후에 실제 생성될 컴포넌트를 이 스냅샷과 비교한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import renderer from "react-test-renderer"
import App from "./App"
describe("App", () => {
let component = null
it("renders correctly", () => {
component = renderer.create(<App />)
})
it("matches snapshot", () => {
const tree = component.toJSON()
expect(tree).toMatchSnapshot()
})
})
|
이 때 추가적으로 react-test-renderer 패키지를 설치해주어야 한다.
react-test-render를 통해 컴포넌트를 테스트 할 수 있는 형태로 만들고, toJSON을 이용하여 이를 JSON형태로 만든다. .toMatchSnapshot() 을 통해 만약 스냅샷이 없다면 스냅샷을 만들고 있다면 이미 만들어진 스냅샷과 비교할 값을 비교한다.
State 테스트
state 테스트를 위한 타이머를 하나 만들자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import React, { Component } from "react"
class Counter extends Component {
state = {
value: 1
}
onIncrease = () => {
this.setState(({ value }) => ({ value: value + 1 }))
}
onDecrease = () => {
this.setState(({ value }) => ({ value: value - 1 }))
}
render() {
const { value } = this.state
const { onIncrease, onDecrease } = this
return (
<div>
<h1>Counter</h1>
<h2>{value}</h2>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
)
}
}
export default Counter
|
< Timer.js >
버튼을 누를 시에 제대로 state가 변화하는지 체크 할 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import React from "react"
import renderer from "react-test-renderer"
import Counter from "./Counter"
describe("Counter 테스트", () => {
let component = null
it("렌더링 테스트", () => {
component = renderer.create(<Counter />)
})
it("스냅샷과 비교", () => {
const tree = component.toJSON()
expect(tree).toMatchSnapshot()
})
it("더하기 기능", () => {
component.getInstance().onIncrease()
expect(component.getInstance().state.value).toBe(2)
const tree = component.toJSON() // re-render
expect(tree).toMatchSnapshot() // 스냅샷 비교
})
it("빼기 기능", () => {
component.getInstance().onDecrease()
expect(component.getInstance().state.value).toBe(1) // value 값이 1인지 확인
const tree = component.toJSON() // re-render
expect(tree).toMatchSnapshot() // 스냅샷 비교
})
})
|
< Timer.test.js >
.getInstance() 를 이용하면 해당 컴포넌트의 대부분의 정보가 나온다.
그중 state는 물론이고 onIncrease()와 같은 메소드도 존재한다.
이를 각각 실행시켜서 비교해 볼 수 있다.
DOM 시뮬레이션 with Enzyme
이번에는 해당 컴포넌트의 버튼을 누르거나 submit을 하는 등의 시뮬레이션을 통해서 테스팅 하는 방법을 알아 볼 것이다.
이를 위해서는 react-test-renderer만의 힘으로는 역부족이고 다음과 같은 패키지가 필요하다.
npm i enzyme
npm i enzyme-adapter-react-16
그리고 다음 내용의 파일을 하나 생성한다.
1
2
3
4
|
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
|
이 때 CRA로 만들시에는 내부 jest 설정으로 인해 파일명이 무조건 setupTests.js 이다.
나머지의 경우에는 root디렉토리에 jest.setup.js 인듯 하다.
테스트를 위한 간단한 폼-리스트 컴포넌트를 만들어 보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import React, { Component } from "react"
import Counter from "./components/Counter"
import Form from "./components/Form"
import List from "./components/List"
class App extends Component {
state = {
names: ["First", "Second"]
}
onInsert = name => {
this.setState(({ names }) => ({ names: names.concat(name) }))
}
render() {
const { names } = this.state
const { onInsert } = this
return (
<div>
<Counter />
<hr />
<h1>List</h1>
<Form onInsert={onInsert} />
<List names={names} />
</div>
)
}
}
export default App
|
< App.js >
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import React, { Component } from "react"
class NameForm extends Component {
state = {
name: ""
}
onChange = e => {
this.setState({
name: e.target.value
})
}
onSubmit = e => {
const { name } = this.state
const { onInsert } = this.props
onInsert(name)
this.setState({
name: ""
})
e.preventDefault()
}
render() {
const { onSubmit, onChange } = this
const { name } = this.state
return (
<form onSubmit={onSubmit}>
<label>Name</label>
<input type="text" value={name} onChange={onChange} />
<button type="submit">Submit</button>
</form>
)
}
}
export default NameForm
|
< Form.js >
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import React, { Component } from "react"
class List extends Component {
static defaultProps = {
names: []
}
renderList() {
const { names } = this.props
const nameList = names.map((name, i) => <li key={i}>{name}</li>)
return nameList
}
render() {
return <ul>{this.renderList()}</ul>
}
}
export default List
|
< List.js >
Form에 입력하고 Submit하면 List가 추가되는 형식이다.
Form 테스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
import React from "react"
import { shallow } from "enzyme"
import Form from "./Form"
describe("NameForm", () => {
let component = null
let changed = null
const onInsert = name => {
changed = name
}
it("renders correctly", () => {
component = shallow(<Form onInsert={onInsert} />)
})
it("matches snapshot", () => {
expect(component).toMatchSnapshot()
})
describe("새로운 텍스트 입력", () => {
it("폼의 여부", () => {
expect(component.find("form").exists()).toBe(true)
})
it("인풋의 여부", () => {
expect(component.find("input").exists()).toBe(true)
})
it("인풋 변화 시뮬레이션", () => {
const mockedEvent = {
target: {
value: "hello"
}
}
component.find("input").simulate("change", mockedEvent)
expect(component.find("input").props().value).toEqual("hello")
})
it("출력 체크", () => {
const mockedEvent = {
preventDefault: () => null
}
component.find("form").simulate("submit", mockedEvent)
expect(component.state().name).toBe("")
expect(changed).toBe("hello")
})
})
})
|
20 번째 줄부터 살펴보자
find()
컴포넌트의 태그를 선택할 수 있다. 이후 exists() 를 이용하여 태그가 있는지 확인하였다.
.simulate(e,obj)
이벤트를 시뮬레이션 한다.
e는 이벤트의 위치 obj는 e가 사용하는 데이터이다.
쉽게 예시의 경우 e.target.value = "hello"인 경우이다.
.props()
react-test-renderer 의 getInstance() 와 달리 enzyme는 props()를 통해 태그의 속성에 접근한다.
마지막으로 42번줄 같은 경우는
state로 input의 내용을 확인했다.
다음 포스팅으로 Next.js와 Redux의 테스팅에 대해 알아보자.
'TDD, CleanCode' 카테고리의 다른 글
Next.js 와 Redux 테스트(Jest, Enzyme)를 해보자. (0) | 2019.08.11 |
---|---|
클린코더스 강의 리뷰 5. TDD 예제 (wordWrap) (0) | 2019.05.21 |
클린코더스 강의 리뷰 5. TDD 예제 (Prime factors) (0) | 2019.05.21 |
클린코더스 강의 리뷰 5. TDD 예제 (Bowling Game) (0) | 2019.05.20 |
클린코더스 강의 리뷰 5. TDD의 기본 예시 (0) | 2019.05.13 |