자바스크립트 코드는 비동기로 수행된다.
테스트 코드에 비동기로 동작해야하는 코드가 있다면, jest가 다른 테스트로 넘어가기 전에 현재 테스트가 완료되었음을 알아야 할 필요가 있다.
jest이는 이를 해결하기 위한 몇 가지 방법이 있다.
테스트에 포함된 코드(함수)가 promise를 반환한다면 jest는 이 promise가 resolve 혹은 reject될 때까지 기다렸다가 수행된다.
reject 될 때는 테스트는 실패한다.
예를 들어fetchData()
함수가 promise를 반환하고 resolve되면 'peanut butter'문자열을 반환한다고 할 때 다음과 같이 작성할 수 있다.
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
async
와 await
을 사용할 수도 있다.
async 테스트를 위해서는 test에 전달할 콜백함수에 async
키워드를 붙여줘야 한다. 위 예시와 동일한 코드는 다음과 같이 작성할 수 있다.
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});
async
와 await
을 .resolves
혹은 .jejects
로 연결할 수 있다.
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});
🚨반드시 promise를 반환하거나 await을 사용해야 한다. return/await 문을 생략하면 fetchData
가 반환하는 promise가 resolve혹은 reject 되기 전에 테스트 코드가 완료될 것이다.
promise가 reject 될 것이 예상되는 코드라면, .catch
메서드를 사용해도 된다.
특정 횟수만큼의 assertions가 호출되는지 검증하기 위해 expect.assertions
를 사용해야 한다.
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});
promise를 사용하지 않고 callback을 사용해도 된다.
이번에는 fetchData
가 promise를 반환하지 않고 callback(null, data)
형태의 콜백함수를 전달받아야 한다고 가정하자. 즉 어떤 데이터를 가져오는 작업이 완료되면 fetchData
가 callback
을 호출하는 것이다.
그리고 이 함수가 문자열 "peanut butter"를 반환한다고 가정한다.
기본적으로 jest는 테스트 코드가 실행흐름 끝에 도달하면(실행이 끝나면) 테스트가 완료되었다고 본다.
test('반환된 데이터는 peanut butter다', () => {
function callback(error, data) {
if (error) {
throw error;
}
expect(data).toBe('peanut butter');
}
fetchData(callback); // Jest가 콜백이 호출되기 전에 테스트가 완료될 가능성이 높다.
});
위와 같이 작성하면 테스트는 콜백이 호출되기 전에 종료될 수 있다.
따라서 jest한테 비동기 처리가 완료되면 콜백을 호출할 것임을 알려야 한다. 이를 위해 테스트 코드를 작성한 콜백 함수에 done 매개변수를 사용해야 한다.
test('반환된 데이터는 peanut butter다', (done) => {
function callback(data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done(); // 테스트가 완료되었음을 Jest에게 알려줌
} catch (error) {
done(error); // 에러 발생 시 Jest에게 에러 전달
}
}
fetchData(callback);
});
이렇게 작성하면 jest는 done 함수가 호출될 때까지 테스트를 완료하지 않는다. 그래서 테스트 코드에 작성한 콜백 함수 내의 검증 코드 expect
와 matcher
가 테스트 완료 전에 제대로 호출된다.
done()
함수가 호출되지 않는다면 테스트가 실패(timeout error)할 것이다.
🚨jest는 테스트 함수에서
done()
콜백을 사용하거나 프로미스를 반환하여 비동기 동작을 나타내야 한다.
동일한 테스트에서 두 가지 방법을 모두 사용하면 테스트에서 원치 않는 동작과 메모리 누수가 발생할 수 있다.
jest는 테스트 코드에done
을 전달하고 테스트에서 코드 내에서 프로미스를 반환하려고 하면 오류를 발생시켜 가능한 혼란과 메모리 누수를 방지한다.
test('done과 프로미스의 잘못된 혼합', (done) => {
fetchData().then((data) => {
expect(data).toBe('peanut butter');
done();
});
return fetchData(); // Jest가 허용하지 않습니다
});
expect 문에서 .resolves
matcher를 사용할 수 있다. 이러면 jest는 해당 프로미스가 resolve 될 때까지 기다린다.
프로미스가 reject 되면 테스트가 자동으로 실패한다.
test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
반드시 assertion을 return해야 한다. 이 반환문을 생략하면 fecthData
에서 반환된 프로미스가 resolve되기 전에 테스트가 완료되고 then()
이 콜백을 실행할 기회조차 없다.
프로미스가 reject될 것 같다면 .rejects
matcher를 사용하면 된다. .resolves
와 유사하게 동작한다. 만약 프로미스가 fulfilled 되면 테스트는 자동으로 실패한다.
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
프로피스가 fullfilled 되거나 다른 값과 함께 reject되면 테스트가 실패한다.