코딩앙마 - Jest 강좌강의 내용을 정리해보려고 한다.
규모가 큰 프로젝트의 경우, 하나하나 테스팅하고 있을 시간이 없다. 따라서 자동화된 테스팅 툴을 사용해 테스팅을 진행한다. 기본적으로 파일 이름 내에 test가 있거나 __tests__
폴더를 테스팅 해주지만, 특정 파일만 테스팅 하고 싶을 경우 마지막에 파일 이름을 넣어준다.
## 특정 파일 확인
yarn test 파일이름
출처: 코딩앙마 - Jest 강좌 #1 소개, 설치 및 간단한 테스트 작성
출처: 코딩앙마 - Jest 강좌 #2 유용한 Matchers
test('코드 설명', () => {
expect(검증할 값).toBe(기대되는 값)
})
toBe
: 원시타입 체크 시 사용toEqual
, toStrictEqual
: 참조타입 체크 시 사용toBeNull
toBeUndefined
toBeDefined
toBeTruthy
toBeFalsy
test('null은 null이야', () => {
expect(null).toBeNull()
})
toBeGreaterThan
: 크다toBeGreaterThanOrEqual
: 크거나 같다toBeLessThan
: 작다toBeLessThanOrEqual
: 작거나 같다JS에서 0.1 + 0.2 !== 0.3
이다.
JS는 메모리에 숫자를 64비트 부동소수점 형식으로 저장하게 되는데, 메모리엔 2진수가 저장되기 때문에 10진수를 2진수로 변환하게 된다. 이 과정에서 무한소수로 변환이 되고 64비트가 넘어가는 부분을 잘라내게 된다. 따라서 결과값은 0.30000000000000004
이다.
Jest에서 이를 해결하기 위해서는 근사치로 비교해주는 toBeCloseTo
함수를 사용하면 된다.
test('0.1 더하기 0.2는 0.3이야.', () => {
expect(fn.add(0.1, 0.2)).toBeCloseTo(0.3) // pass
})
toMatch(정규표현식)
test('Hello World에는 w가 있어.', () => {
expect('Hello World').toMatch(/w/i) // pass
})
toContain()
test('userList에 test가 있어?', () => {
const userList = ['Tom', 'Jane', 'Kai']
expect(userList).toContain('test') // failed
})
toThrow()
: 에러가 발생만 하면 passtoThrow(에러 이름)
: 특정 에러인지 확인test('에러 발생 확인', () => {
expect(() => fn.getThrowError()).toThrow('error')
})
출처: 코딩앙마 - Jest 강좌 #3 비동기 코드 테스트
엔진이 코드 하단부에 도달하면 실행이 그대로 끝이 나서 비동기 코드가 실행되지 않는다.
이를 위해 test('설명', 콜백) 에서 콜백 함수 인자로 특정 키워드(e.g, done
)를 넣고, 특정 키워드에 엔진이 도달했을 때 끝나도록 설정한다.
const fn3 = {
getName: (callback) => { // callback을 인자로 받고 3초 후 콜백함수를 실행함
const name = 'Kim'
setTimeout(() => {
callback(name)
}, 3000)
}
}
// fn.test.js
test('3초 후 Kim이라는 이름을 출력', (done) => {
function callback(name) {
expect(name).toBe('Kim')
done() // 비동기 함수가 실행되고 done까지 만났을 때 비로소 종료
}
fn.getName(callback)
})
Jest는 프로미스가 resolve, reject될 때까지 기다려주기 때문에 종료를 위한 특정 키워드를 쓸 필요가 없다. 대신에 프로미스를 리턴하는 함수 앞에 return
을 시켜줘야 한다.
const fn3 = {
getPuppy: () => {
const puppy = 'pup'
return new Promise((res, rej) => {
setTimeout(() => {
res(puppy) // 프로미스 return
}, 2000)
})
}
}
fn.test.js
test('2초 후 puppy "pup"을 출력', () => {
return fn3.getPuppy().then((puppy) => {
expect(puppy).toBe('pup')
})
})
프로미스 패턴을 쓸 수 있는 것은 좋지만, 여전히 콜백패턴이 남아있다. 그럴 땐 resolves
혹은 rejects
체이닝으로 then 역할을 해보자.
test('2초 후 puppy "pup"을 출력', () => {
return expect(fn3.getPuppy()).resolves.toBe('pup')
})
async, await도 프로미스를 사용하기 때문에 상단 getPuppy 메서드를 그대로 사용하겠다.
test('2초 후 puppy "pup"을 출력 with asnyc, await', async () => {
const puppy = await fn3.getPuppy()
expect(puppy).toBe('pup')
})
출처: 코딩앙마 - Jest 강좌 #4 테스트 전후 작업
beforeEach
: 각 테스트 함수가 실행되기 전 실행되는 함수afterEach
: 각 테스트 함수가 실행된 후 실행되는 함수beforeAll
: 모든 함수가 실행되기 전 한번만 실행되는 함수afterAll
: 모든 함수가 실행된 후 한번만 실행되는 함수⭐️ describe 밖의 beforeEach가 안의 beforeEach 보다 항상 먼저 실행된다.
describe("User 관련 작업", () => { // 관련작업을 한 데 묶을 수 있다.
let user = {}
beforeAll(async () => {
// 작업 전 user Db 가져오기
user = await fn3.connectUserDb()
})
afterAll(async () => {
return fn3.disonnectUserDb()
})
test('이름은 Kim', () => {
expect(user.name).toBe('Kim')
})
})
test.only(설명, () => {})
: 특정 테스트만 확인test.skip(설명, () => {})
: 특정 테스트만 스킵출처: 코딩앙마 - Jest 강좌 #5 목 함수(Mock Functions)
const mockFunc = jest.fn() // mock 함수 만들기
mockFunc.mock.calls // 함수의 호출 횟수, 호출된 인자값 확인 가능, [[인자], [인자, 인자], [인자]]
mockFunc.mock.results // 리턴된 값 확인, [{ type: 'return', value: 어쩌구 }]
mockReturnValueOnce
: mock 함수의 리턴 값 설정mockReturnValue
: mock 함수의 마지막 요소의 리턴값 설정mockFunc.
.mockReturnValueOnce(바꿀 값)
.mockReturnValueOnce(바꿀 값)
.mockReturnValue(바꿀 값)
mockResolvedValue
: 비동기 함수 흉내내기호출될 때마다 실제로 db등에 영향을 미치는 함수가 있다고 가정하자. 테스트를 하기 위해 함수를 직접 호출하고 db에 접속해 다시 되돌리는 일은 번거로울 수밖에 없다. 그럴 때 jest.mock()
을 활용한다.
jest.mock()
은 불러온 함수를 mocking module로 만들어 함수를 실제로 호출되지 않는다.
const func = require('./fn')
jest.mock('./fn') // mock module로 만듦
func.createUser.mockReturnValue({ name: 'Kim' }) // 함수의 리턴값을 변경
test('user 생성', () => {
const user = fn.createUser('Kim')
expect(user.name).toBe('Kim')
})
toBeCalled()
: 한번 이상 호출됐는지 체크toBeCalledTimes(횟수)
: 정확한 호출횟수 체크toBeCalledWith(인수...)
: 인수 체크lastCalledWith(인수...)
: 마지막으로 실행된 함수의 인수만 체크기타 expect 관련 함수들은 jest 공홈에서 확인할 수 있다.
Accessble to Everyone.
getByRole
getByRole('button', {name: /submit/i})
getByLabelText
getByAltText
getByTitle
getByTestId
...
fireEvent로 버튼 클릭 테스팅 시 버튼이 focus 되지 않는다. 이는 테스팅 환경에서 실제 유저의 action을 최대한 반영하지 못한다는 의미이다. fireEvent보다 userEvent를 사용하면 유저의 action을 좀 더 실제같이 반영할 수 있기 때문에 useEvent를 사용하자.
서버에 보내는 요청을 가로채서 Mock Service Worker에서 처리하고 모의 응답을 보내주도록 할 때 사용.
handler 생성
getBy | queryBy | findBy |
---|---|---|
쿼리에 일치하는 노드 반환. 일치하는 노드가 없을 경우 오류 발생 | 쿼리에 일치하는 노드 반환. 일치하는 노드가 없을 경우 null 반환 둘 이상의 일치항목이 발견되면 오류 발생 (그땐 queryAllBy 사용하기) | 쿼리에 일치하면 Promise 반환. 일치하는 요소가 없거나 1000ms 후 둘 이상의 요소가 발견되면 reject 반환 |
render(<App />)
등으로 먼저 호출해줘야 한다.// package.json
"devDependencies": {
"eslint-plugin-jest-dom": "^4.0.1",
"eslint-plugin-testing-library": "^5.3.1"
}
// .eslintrc.json
{
"plugins": ["testing-library", "jest-dom"],
"extends": [
"react-app",
"react-app/jest",
"plugin:testing-library/react",
"plugin:jest-dom/recommended"
]
}
resetHandlers
를 활용해 핸들러를 초기화 시켜준다.server.resetHandlers(
rest.get(`api 요청 주소`, (req, res, ctx) =>
res(ctx.status(500))
)
);
userEvent 초기화 시켜주기
현재 소스코드보다 위에서 같은 DOM 요소(input / textarea)의 userEvent를 사용했었다면 userEvent.clear(앨리먼트)
해주는 것이 좋다.
Provider를 사용할 땐 테스트 코드에서 wrapper를 설정해주기
render(<Type orderType="products" />, { wrapper: OrderContextProvider });
toHaveTextContent
: 해당 요소가 text를 갖고 있는지 확인 toBeInTheDocument
: 해당 요소가 screen에 있는지 확인