매처로 값 검증하기
매처로 값 검증하기 (Using Matchers)
expect 에 '매처(matcher)'를 붙여 값이 어떤 조건을 만족하는지 단언하는 게 Vitest 의 핵심 사용법이에요. 이 페이지에서는 실제로 자주 쓰이는 매처들을 소개할게요. 완전한 목록이 필요하면 Expect API 레퍼런스를 참고하세요. 매처를 고르는 기준은 '내가 확인하려는 조건을 가장 정확히 표현하는 것'이에요 — 의미가 애매하면 버그를 놓치기 쉬우니까요.
자주 쓰는 매처
값을 테스트하는 가장 단순한 방법은 정확한 동일성(equality) 검사예요. expect(2 + 2).toBe(4) 라고 쓰면 toBe 매처가 그 값이 정확히 4 인지 Object.is 로 확인해요.
import { expect, test } from 'vitest'
test('two plus two is four', () => {
expect(2 + 2).toBe(4)
})
숫자·문자열·불리언 같은 원시값에는 이 방식이 잘 맞아요. 그런데 객체를 비교할 때 toBe 는 동일성을 봐요. 즉 메모리상의 정확히 같은 객체인지 보지, 같은 모양인지는 보지 않아요. 그런 비교는 toEqual 몫이에요. 객체의 모든 필드나 배열의 모든 요소를 재귀적으로 비교하되, 객체 동일성은 무시해요:
test('object assignment', () => {
const data = { one: 1 }
data.two = 2
expect(data).toEqual({ one: 1, two: 2 })
})
차이가 더 잘 드러나는 예시를 볼게요. 내용이 같은 두 객체는 toEqual 이지만 toBe 는 아니에요:
test('toBe vs toEqual', () => {
const a = { name: 'Alice' }
const b = { name: 'Alice' }
// These are different objects in memory
expect(a).not.toBe(b)
// But they have the same structure
expect(a).toEqual(b)
})
toStrictEqual 는 toEqual 보다 세 가지 면에서 더 엄격해요. undefined 프로퍼티를 검사하고, 희소 배열(sparse array)과 undefined 값을 구분하며, 객체의 타입까지(모양뿐 아니라) 확인해요:
test('toEqual vs toStrictEqual', () => {
// toEqual ignores undefined properties
expect({ a: 1 }).toEqual({ a: 1, b: undefined })
// toStrictEqual catches them
expect({ a: 1 }).not.toStrictEqual({ a: 1, b: undefined })
// toEqual doesn't check object types
class User {
constructor(name) {
this.name = name
}
}
expect(new User('Alice')).toEqual({ name: 'Alice' })
expect(new User('Alice')).not.toStrictEqual({ name: 'Alice' })
})
::: tip
경험칙 하나를 정리하면 이래요. 원시값(숫자·문자열·불리언)에는 toBe, 구조 비교에는 toEqual, 타입이나 명시적 undefined 값까지 신경 써야 할 때는 toStrictEqual 을 쓰면 돼요.
:::
어떤 매처든 앞에 .not 을 붙이면 부정할 수 있어요. '~가 아님'을 확인하고 싶을 때 유용하죠:
test('adding positive numbers is not zero', () => {
expect(1 + 2).not.toBe(0)
})
Truthiness
테스트를 하다 보면 undefined, null, false 를 구분해야 할 때가 있고, 정확한 값이 아니라 그냥 truthy 인지 falsy 인지만 알고 싶을 때도 있어요. Vitest 는 두 상황 모두에 매처를 제공해요:
toBeNull—null만 매칭toBeUndefined—undefined만 매칭toBeDefined—toBeUndefined의 반대.undefined가 아닌 모든 값에 통과toBeTruthy—if문이 참으로 취급할 모든 것에 매칭toBeFalsy—if문이 거짓으로 취급할 모든 것에 매칭
확인하려는 대상을 가장 정확히 설명하는 매처를 골라야 해요. 진짜로 원하는 게 toBeDefined 인데 toBeTruthy 를 쓰면 버그를 숨길 수 있어요. 0 과 "" 은 둘 다 defined 이면서 falsy 이니까요.
test('null checks', () => {
const n = null
expect(n).toBeNull()
expect(n).toBeDefined()
expect(n).toBeFalsy()
expect(n).not.toBeTruthy()
expect(n).not.toBeUndefined()
})
test('zero', () => {
const z = 0
expect(z).toBeDefined() // passes: 0 is defined
expect(z).toBeFalsy() // passes: 0 is falsy
expect(z).not.toBeNull() // passes: 0 is not null
})
숫자
대부분의 숫자 비교는 직관적이에요. Vitest 는 크다, 작다, 같다 검사에 기대하는 매처를 그대로 제공해요:
test('number comparisons', () => {
const value = 2 + 2
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3.5)
expect(value).toBeLessThan(5)
expect(value).toBeLessThanOrEqual(4.5)
// For exact equality, both toBe and toEqual work the same for numbers
expect(value).toBe(4)
expect(value).toEqual(4)
})
부동소수점 산술에는 자주 걸리는 함정이 하나 있어요. JavaScript 에서 0.1 + 0.2 는 정확히 0.3 이 아니에요(0.30000000000000004). 그래서 toBe(0.3) 검사는 실패해요. 대신 toBeCloseTo 를 쓰세요. 작은 반올림 오차 안에서 숫자를 비교해줘요:
test('adding floating point numbers', () => {
const value = 0.1 + 0.2
// This won't work because of floating point rounding
// expect(value).toBe(0.3)
// This works
expect(value).toBeCloseTo(0.3)
})
문자열
toMatch 로 문자열을 정규식에 대조할 수 있어요. 정확한 값보다 패턴이 중요할 때, 예를 들어 에러 메시지에 특정 단어가 들어있는지나 URL 이 특정 형식을 따르는지 확인할 때 특히 유용해요:
test('there is no I in team', () => {
expect('team').not.toMatch(/I/)
})
test('version string matches semver format', () => {
expect('[email protected]').toMatch(/vitest@\d+\.\d+\.\d+/)
})
배열과 이터러블
toContain 은 배열(또는 Set 같은 이터러블)에 특정 항목이 들어있는지 확인해요. 비교에 === 를 쓰기 때문에 원시값에는 잘 맞아요:
test('the shopping list has milk in it', () => {
const shoppingList = ['milk', 'bread', 'eggs', 'butter']
expect(shoppingList).toContain('milk')
expect(new Set(shoppingList)).toContain('milk')
})
배열이 특정 구조의 객체를 포함하는지 확인해야 한다면 toContainEqual 을 쓰면 돼요. 배열 안의 개별 항목에 대해 toEqual 처럼 동작하죠.
객체
객체를 테스트할 때 모든 프로퍼티를 전부 명시하고 싶지 않고 중요한 필드 몇 개만 확인하고 싶은 경우가 많아요. toMatchObject 는 정확히 그런 일을 해줘요. 지정한 프로퍼티를 최소한 포함하는지만 확인하고, 추가 프로퍼티는 무시해요:
test('user has expected fields', () => {
const user = {
id: 1,
name: 'Alice',
email: '[email protected]',
createdAt: '2024-01-01'
}
// We only care about name and email here
expect(user).toMatchObject({
name: 'Alice',
email: '[email protected]',
})
})
개별 프로퍼티, 특히 중첩된 프로퍼티를 확인할 때는 toHaveProperty 가 더 읽기 좋아요. 점으로 구분한 경로와, 선택적으로 기대값을 넘기면 돼요:
test('object has property', () => {
const user = {
name: 'Alice',
address: { city: 'Paris', zip: '75001' }
}
expect(user).toHaveProperty('name')
expect(user).toHaveProperty('name', 'Alice')
expect(user).toHaveProperty('address.city', 'Paris')
expect(user).toHaveProperty('address.zip')
})
비대칭 매처 (Asymmetric Matchers)
정확한 값은 모르지만 타입이나 모양은 아는 경우가 있어요. 비대칭 매처를 쓰면 정확한 내용을 못 박지 않고 '이런 모양이어야 한다'고 서술할 수 있어요. toEqual 이나 toMatchObject 처럼 깊은 비교를 하는 매처 안에서 그대로 동작해요:
test('user has the right shape', () => {
const user = createUser('Alice')
expect(user).toEqual({
id: expect.any(Number),
name: 'Alice',
email: expect.stringContaining('@'),
roles: expect.arrayContaining(['viewer']),
})
})
가장 흔한 비대칭 매처들은 이래요:
expect.any(Constructor)— 주어진 생성자로 만들어진 모든 값 매칭 (Number,String,Array등)expect.stringContaining(str)— 주어진 부분 문자열을 포함하는 문자열 매칭expect.stringMatching(regex)— 정규식에 매칭되는 문자열expect.arrayContaining(arr)— 기대 배열의 모든 항목을 포함하는 배열(순서 무관, 추가 항목 허용)expect.objectContaining(obj)— 지정한 프로퍼티를 최소한 포함하는 객체
예외 (Exceptions)
함수가 에러를 던지는지 확인하려면 toThrow 를 써요. 호출을 다른 함수로 감싸야 하는데, 그래야 Vitest 가 에러를 잡아서 테스트를 깨뜨리지 않게 할 수 있어요:
function compileCode(code) {
if (code === '') {
throw new Error('Cannot compile empty string')
}
return code
}
test('compiling an empty string throws', () => {
// Check that it throws at all
expect(() => compileCode('')).toThrow()
// Check the error message
expect(() => compileCode('')).toThrow('Cannot compile empty string')
// Check the message with a regex
expect(() => compileCode('')).toThrow(/empty string/)
})
::: tip
() => compileCode('') 처럼 감싸는 함수가 중요해요. expect(compileCode('')).toThrow() 로 쓰면 expect 가 잡기 전에 에러가 던져져서, 테스트가 처리되지 않은 에러로 실패해 버려요.
:::
소프트 단언 (Soft Assertions)
보통 실패한 단언은 그 자리에서 테스트를 멈춰요. 대부분은 이게 유용하지만, 서로 독립적인 여러 가지를 한꺼번에 확인하고 싶어서 실패를 하나씩 고치기보다 한 번에 다 보고 싶을 때가 있어요. expect.soft 는 정확히 그렇게 해요. 실패를 기록하되 테스트는 계속 진행시켜요:
test('check multiple fields', () => {
const user = { name: 'Alice', age: 30, role: 'admin' }
expect.soft(user.name).toBe('Alice')
expect.soft(user.age).toBe(25) // this fails but execution continues
expect.soft(user.role).toBe('admin')
// the test report will show that age didn't match
})
API 응답이나 복잡한 객체의 모양을 검증할 때 특히 유용해요. 여러 필드가 동시에 틀려 있을 수 있으니까요.
더 알아보기 (Learn more)
- Expect API Reference — 모든 매처의 완전한 목록
- Extending Matchers — 나만의 매처 추가하기
- Writing Tests — 테스트 작성의 기본 구조