React 테스트 (Jest, Enzyme)
React 테스트 (Jest, Enzyme)
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의 테스팅에 대해 알아보자.
from http://moonsupport.tistory.com/249 by ccl(A) rewrite - 2020-03-07 10:55:08
댓글
댓글 쓰기