Jest로 error 검증하기
카테고리: feDevEnvironment
✏️ 글의 목적: 아이디와 비밀번호를 POST 요청 보내고, 정보가 맞지 않을 경우 error를 throw 하는 테스트 코드를 만들어 보았다. 처음 만들다 보니 고생을 며칠 했다. 다음에도 이러한 postLogin() 함수의 테스트 코드를 만들 나를 위해, 그리고 다른 개발자를 위하여 정보를 제공하고자 한다.
// api.test.js
import {
postLogin,
} from './api';
import LOGINTOKENS from '../../fixtures/loginTokens';
import LOGINFAILDATA from '../../fixtures/loginFailData';
describe('api', () => {
const mockFetch = (data) => {
global.fetch = jest.fn().mockResolvedValue({
async json() { return data; },
});
};
describe('postLogin', () => {
beforeEach(() => {
mockFetch(LOGINTOKENS);
});
it('returns loginTokens', async () => {
const data = await postLogin({
email: '',
password: '',
});
expect(data).toEqual(LOGINTOKENS);
});
context('when login fails', () => {
beforeEach(() => {
mockFetch(LOGINFAILDATA);
});
it('throw an error', async () => {
try {
await postLogin({
email: 'tester@example.com',
password: '',
});
} catch (e) {
expect(e.message).toBe('INVALID_PASSWORD');
}
});
});
});
});
위의 코드와 같이 it을 작성하면 api.js에서 try …catch 구조를 사용하지 않아도
위의 테스트에서는 정상적으로 통과했다고 테스트를 완료한다. 나는 아직 api.js의 postLogin() 함수에서 try …catch 구조를 사용하지도 않았는데 테스트가 정상적으로 수행만 돼버려도 해당 테스트를 통과하게 되는 것이다.(거짓으로 테스트를 통과한 것이다🥹) 이를 바로잡기 위해서 아래의 코드가 필요했다.
// api.test.js
import {
postLogin,
} from './api';
import LOGINTOKENS from '../../fixtures/loginTokens';
import LOGINFAILDATA from '../../fixtures/loginFailData';
describe('api', () => {
const mockFetch = (data) => {
global.fetch = jest.fn().mockResolvedValue({
async json() { return data; },
});
};
describe('postLogin', () => {
beforeEach(() => {
mockFetch(LOGINTOKENS);
});
it('returns loginTokens', async () => {
const data = await postLogin({
email: '',
password: '',
});
expect(data).toEqual(LOGINTOKENS);
});
context('when login fails', () => {
beforeEach(() => {
mockFetch(LOGINFAILDATA);
});
it('throw an error', async () => {
let errorExcept = null;
try {
await postLogin({
email: 'tester@example.com',
password: '',
});
} catch (e) {
expect(e.message).toBe('INVALID_PASSWORD');
errorExcept = e;
}
expect(errorExcept).not.toBeNull();
});
});
});
});
위의 코드에서는 it 문 안에 errorExpect 변수를 만들고, catch가 실행되면 errorExcept에 값을 재할당
하여 catch가 실행되었는지 테스트할 수 있게 된다.
이렇게 errorExcept 변수를 선언하고 catch가 실행될 경우 값을 재할당하는 방식은 let을 최대한 지양해서 프로젝트를 만들고 싶었던 나의 방향과는 맞지 않았다. 그리고 postLogin() 함수를 테스트하기 위해 기존의 코드와 관계없는 부가적인 변수를 선언하는 것도 조금은 아쉬움이 생겼다.
그러다가 이동욱 님이 작성하신 블로그 글을 발견하게 되었고, 여기서 더 나은 방법을 찾을 수 있었다. 그것은 바로 expect.rejects.toThrowError
를 사용하는 것이다.
💡 이동욱님 블로그: Jest로 Error 검증시 catch 보다는 expect
// api.test.js
import {
postLogin,
} from './api';
import LOGINTOKENS from '../../fixtures/loginTokens';
import LOGINFAILDATA from '../../fixtures/loginFailData';
describe('api', () => {
const mockFetch = (data) => {
global.fetch = jest.fn().mockResolvedValue({
async json() { return data; },
});
};
describe('postLogin', () => {
beforeEach(() => {
mockFetch(LOGINTOKENS);
});
it('returns loginTokens', async () => {
const data = await postLogin({
email: '',
password: '',
});
expect(data).toEqual(LOGINTOKENS);
});
context('when login fails', () => {
beforeEach(() => {
mockFetch(LOGINFAILDATA);
});
it('throw an error', async () => {
await expect(async () => {
await postLogin({
email: 'tester@example.com',
password: '',
});
}).rejects.toThrowError(new Error('INVALID_PASSWORD'));
});
});
});
});
expect.rejects.toThrowError() matcher를 사용하면, 비동기 함수에서 예상한 오류를 발생하는지 확인할 수 있다. 테스트되는 함수가 프로미스를 반환하고, 해당 프로미스가 reject되면 matcher가 통과
된다. 위의 실제 코드보다 조금 더 간단한 코드를 살펴보자.
async function asyncFunction() {
throw new Error('This is an error');
}
test('async function throws an error', async () => {
await expect(asyncFunction()).rejects.toThrowError('This is an error');
});
위 예제에서 asyncFunction()은 Error 객체를 throw 한다. 그렇기 때문에 rejects.toThrowError() matcher를 사용하면, 예상한 메시지와 일치하는지 테스트하게 된다.
댓글남기기