FE
Matchers - Jest
| 개요
우아한테크코스를 하면서 Jest를 활용한 테스트를 진행하게 되어서, 이에 대해 공부하고 배운 내용을 정리하고자 한다.
먼저, Matchers는 Jest에서 테스트 값의 일치 여부를 확인하기 위한 도구이다. 하나하나씩 살펴보자.
| expect()
expect() 함수는 "기대 객체(expectation object)"를 반환한다.
여기에 .toBe(), .toEqual() 등 다양한 matcher를 연결하여 검증한다.
// 예시
test('2 + 2는 4다', () => {
expect(2 + 2).toBe(4);
});| 다양한 Matchers
1) Common Matchers
Matcher | 설명 |
toBe | Object.is()를 사용하여, 정확한 동일성을 비교한다. |
toEqual | 객체나 배열의 모든 필드 재귀를 비교한다. |
toStrictEqual | toEqual보다 엄격하며, undefined 속성/배열희소성/타입까지 검사한다. |
not | 반대 조건을 테스트할 때 사용한다.(예 : expect().not.toBe(y) ) |
// 예시
expect(data).toEqual({ one: 1, two: 2 });
expect(a + b).not.toBe(0);2) Truthiness Matchers (참/거짓 관련)
Matcher | 설명 |
toBeNull | null만 일치 |
toBeUndefined | undefined만 일치 |
toBeDefined | undefined가 아님 |
toBeTruthy | if문에서 true로 평가되는 값 |
toBeFalsy | if문에서 false로 평가되는 값 |
// 예시
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).toBeFalsy();3) Numbers (숫자 비교)
Matcher | 설명 |
toBeGreaterThan | 초과 |
toBeGreaterThanOrEqual | 이상 |
toBeLessThan | 미만 |
toBeLessThanOrEqual | 이하 |
toBeCloseTo | 부동소수점 비교 시 사용 (반올림 오차 방지) |
// 예시
const value = 0.1 + 0.2;
expect(value).toBeCloseTo(0.3);4) Strings (문자열)
Matcher | 설명 |
toMatch | 해당 문자열와 동일하다. |
정규식을 활용해 문자열 내 일치 여부를 검사할 수도 있다.
// 예시
expect('team').not.toMatch(/I/);
expect('Christoph').toMatch(/stop/);5) Arrays & Iterables
Matcher | 설명 |
toContain | 해당 요소를 포함한다. |
배열이나 Set 등에서 특정 항목 포함 여부를 검사할 수 있다.
// 예시
const shoppingList = ['diapers', 'milk'];
expect(shoppingList).toContain('milk');
expect(new Set(shoppingList)).toContain('milk');| Exceptions (예외 처리)
특정 함수가 에러를 던지는지 테스트할 때 사용한다.
주의: toThrow()는 함수를 직접 호출하지 않고 래핑해야 한다.
// 예시
function compileAndroidCode() {
throw new Error('you are using the wrong JDK!');
}
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow('wrong JDK');
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK!$/);| 비동기 처리
Javascript는 비동키 코드(asynchronous code)가 매우 흔하다. Jest는 테스트가 완료되기 전에 코드 실행이 끝났는지를 알아야 하므로, 비도기 코드를 올바르게 테스트하기 위한 여러 방식을 제공한다.
즉, "Jest는 "이 테스트가 끝났다"라는 신호를 알아야 다음 테스트로 넘어간다.
1) Promises (프로미스 방식)
프로미스(Promise)를 반환하면, Jest는 해당 프로미스가 resolve될 때까지 기다린다.
reject되면 테스트는 실패한다.
반드시 return해야 한다.
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});2) Aynsc / Await 방식
async 키워드를 붙여 비동기 함수로 테스트를 작성할 수 있다.
await으로 Promises의 결과를 기다린다.
// 예시
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 (error) {
expect(error).toMatch('error');
}
});3) .resolves / .rejects 사용
: Promise를 직접 expect()와 함께 사용할 수도 있다.
return 또는 await을 반드시 사용해야 한다.-> 그렇지 않으면, 테스트가 너무 빨리 종료된다.
.resolve : 성공 시 값을 검증
.reject : 실패 시 에러를 검증
test('데이터는 peanut butter이다', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('fetch가 에러로 실패한다', async () => {
await expect(fetchData()).rejects.toMatch('error');
});4) Callbacks (콜백 방식)
: 어떤 함수가 콜백을 인자로 받는 형태라면, 다음과 같이 테스트한다.
done()이 호출되어야 테스트가 끝난다.
done()이 호출되지 않으면, 타임아웃 에러가 발생한다.
expect 실패 시에도 done(error)로 넘겨야 로그가 보인다.
동일한 테스트에서 done()과 Promise를 함께 사용하면, Jest가 에러를 던진다.
// 올바른 예시 : done을 사용
test('데이터는 peanut butter이다', done => {
function callback(error, data) {
if (error) {
done(error); // 에러 발생 시 테스트 종료
return;
}
try {
expect(data).toBe('peanut butter');
done(); // 성공 시 호출
} catch (error) {
done(error); // expect 실패 시 처리
}
}
fetchData(callback);
});// 잘못된 예시 : 즉시 테스트가 끝남.
test('데이터는 peanut butter이다', () => {
function callback(error, data) {
if (error) throw error;
expect(data).toBe('peanut butter');
}
fetchData(callback);
});| 파라미터화 테스트
테스트 중 입력값만 다르고 나머지 로직은 동일한 경우, 매번 새로운 테스트 함수를 만드는 방법 대신에, 테스트 데이터를 반복문처럼 순회하며 실행할 수 있다.
Jest는 이를 위해 test.each()와 describe.each() 두 가지 메서드를 제공한다.
1) 예시 함수 : 애너그램(Anagram) 판별기
: 두 개의 단어나 문장이 서로 애너그램 관계인지 확인 (예를 들어, 'listen'과 'silent'는 애너그램 관계이며, 두 단어 모두 'l', 'i', 's', 't', 'e', 'n' 문자를 하나씩 포함하고 있다.)
function areAnagrams(first, second) {
const counter = {};
for (const ch of first) counter[ch] = (counter[ch] || 0) + 1;
for (const ch of second) counter[ch] = (counter[ch] || 0) - 1;
return Object.values(counter).every(cnt => cnt === 0);
}2) 기존 테스트의 문제점
test('car and bike are not anagrams', () => {
expect(areAnagrams('car', 'bike')).toBe(false);
});
test('car and arc are anagrams', () => {
expect(areAnagrams('car', 'arc')).toBe(true);
});테스트 내용이 중복된다.
데이터만 다를 뿐, 구조는 동일하여 유지보수가 어려워진다.
3) test.each() - 반복되는 테스트를 간단하게
: 여러 데이터셋을 배열로 전달하면, Jest가 각 행(row)에 대해 테스트를 반복 실행한다.
test.each([
['cat', 'bike', false],
['car', 'arc', true],
['cat', 'dog', false],
['cat', 'act', true],
])('areAnagrams(%s, %s) returns %s', (first, second, expected) => {
expect(areAnagrams(first, second)).toBe(expected);
});%s는 포맷팅 문자열로, 테스트 이름에 데이터가 자동으로 삽입되어, 테스트 로그가 더욱 직관적으로 출력된다.
// 결과 예시
✓ areAnagrams(cat, bike) returns false
✓ areAnagrams(car, arc) returns true
...4) describe.each() - 여러 테스트 그룹 반복
조금 더 복잡한 상황, 즉 여러 테스트 세트를 여러 번 실행해야 할 때 사용한다.
예를 들어, 옵션(ignoreCase, ignoreSpaces)에 따라 결과가 달라지는 경우에 활용한다.
function areAnagrams(first, second, options = { ignoreCase: false, ignoreSpaces: false }) {
if (options.ignoreCase) {
first = first.toLowerCase();
second = second.toLowerCase();
}
if (options.ignoreSpaces) {
first = first.replace(/ /g, '');
second = second.replace(/ /g, '');
}
// ... 동일 로직
}// 예시 - 대소문자 무시 옵션 테스트
describe.each([
['Cat', 'Act'],
['Save', 'Vase'],
['Elbow', 'Below'],
])('areAnagrams(%s, %s)', (first, second) => {
it('true with ignoreCase option', () => {
expect(areAnagrams(first, second, { ignoreCase: true })).toBe(true);
});
it('false without ignoreCase option', () => {
expect(areAnagrams(first, second)).toBe(false);
});
});// 예시 - 공백 무시 옵션 테스트
describe.each([
['dormitory', 'dirty room'],
['conversation', 'voices rant on'],
])('areAnagrams(%s, %s)', (first, second) => {
it('true with ignoreSpaces option', () => {
expect(areAnagrams(first, second, { ignoreSpaces: true })).toBe(true);
});
it('false without ignoreSpaces option', () => {
expect(areAnagrams(first, second)).toBe(false);
});
});// 결과 예시
✓ areAnagrams(Cat, Act) return true with ignoreCase option
✓ areAnagrams(dormitory, dirty room) return true with ignoreSpaces option| 마무리
Jest의 강력한 기능들을 잘 활용하면, 테스트 코드도 더 이상 번거로운 작업이 아니라 개발 품질을 지탱하는 핵심 도구가 된다고 생각한다.
단순한 테스트 작성을 넘어, 코드의 신뢰성과 유지보수성을 높이는 방향으로 Jest를 적극 활용해봐야겠다.