Navigation

4. Vitest 단언과 mock

값, 오류, 비동기 완료를 계약에 맞는 matcher로 확인합니다. 검증 대상은 실제로 실행하고 필요한 의존 경계만 대체합니다.

4.1 값과 오류의 계약을 맞는 matcher로 끝까지 확인합니다

느슨한 비교와 완료되지 않은 비동기 작업으로 생기는 거짓 통과를 막습니다.

값의 동일성은 toBe, 전체 구조의 정확성이 계약이면 toStrictEqual로 확인합니다. 일부 필드만 계약이면 부분 비교를 사용하고 그 밖의 필드까지 고정하지 않습니다.

동기 예외는 함수를 넘겨 toThrow로 검사하고 Promise의 성공과 거부는 resolves와 rejects를 await합니다. 오류가 없으면 단언도 실행되지 않는 catch 블록으로 예외 검사를 대신하지 않습니다. 테스트 종료 후에도 남는 Promise나 처리되지 않은 rejection이 없도록 비동기 작업을 기다립니다. 러너가 Vitest이면 실제 Vitest API와 프로젝트 설정을 사용합니다.

참고: 공식 문서.

Promise가 성공해도 catch의 단언이 실행되지 않아 통과합니다.

src/service/orders.test.tsts
import { expect, test } from "vitest";

import { getOrder } from "@/service/orders";

test("없는 주문은 오류를 반환한다", async () => {
	/**
	 * GIVEN
	 * 존재하지 않는 주문 ID
	 */
	const orderId = "missing";

	/**
	 * WHEN
	 * 주문 조회 시작
	 */
	const request = getOrder(orderId);

	/**
	 * THEN
	 * 약속된 오류의 비동기 완료
	 */
	await request.catch((error: unknown) => {
		expect(error).toBeDefined();
	});
});

거부와 오류 문구를 await한 matcher로 확인합니다.

src/service/orders.test.tsts
import { expect, test } from "vitest";

import { getOrder } from "@/service/orders";

test("없는 주문은 오류를 반환한다", async () => {
	/**
	 * GIVEN
	 * 존재하지 않는 주문 ID
	 */
	const orderId = "missing";

	/**
	 * WHEN
	 * 주문 조회 시작
	 */
	const request = getOrder(orderId);

	/**
	 * THEN
	 * 약속된 오류의 비동기 완료
	 */
	await expect(request).rejects.toThrow("주문이 없습니다");
});

4.2 검증 대상의 실제 로직을 실행하고 필요한 의존성만 대체합니다

mock 자체의 동작을 검증하며 실제 결함을 놓치는 일을 막습니다.

검증 대상 함수와 그 계약에 포함된 로직을 실제로 실행합니다. 시간, 파일, 네트워크처럼 통제가 필요한 경계만 대체하고 내부 함수의 호출 순서에 기대값을 묶지 않습니다.

모듈을 대체할 때는 실제 export 계약에 맞는 mock을 만들고 타입 검사가 되는 import 형태를 사용합니다. 결과로 관찰할 수 없는 외부 부수효과가 계약일 때는 호출 인자와 횟수를 검사할 수 있습니다. 단순한 내부 리팩터링에도 같은 외부 결과를 내는 테스트는 유지돼야 합니다.

참고: 공식 문서.

검증할 함수 자체를 대체하고 대체값을 확인합니다.

src/page/orders/_function/to-order-rows.test.tsts
import { expect, test, vi } from "vitest";

import { toOrderRows } from "@/page/orders/_function/to-order-rows";

vi.mock(import("@/page/orders/_function/to-order-rows"), () => ({
	toOrderRows: vi.fn(() => [{ id: "order-1", quantityLabel: "2.00" }]),
}));

test("수량을 표시용 행으로 변환한다", () => {
	/**
	 * GIVEN
	 * 변환할 실제 주문 입력
	 */
	const orders = [{ id: "order-1", quantity: 2, status: "open" as const }];

	/**
	 * WHEN
	 * 표시용 주문 행 변환
	 */
	const rows = toOrderRows(orders);

	/**
	 * THEN
	 * ID와 표시 문자열의 변환 계약
	 */
	expect(rows).toStrictEqual([{ id: "order-1", quantityLabel: "2.00" }]);
});

입력과 외부 의존 경계만 통제하고 실제 변환을 실행합니다.

src/page/orders/_function/to-order-rows.test.tsts
import { expect, test } from "vitest";

import { toOrderRows } from "@/page/orders/_function/to-order-rows";

test("수량을 표시용 행으로 변환한다", () => {
	/**
	 * GIVEN
	 * 변환할 실제 주문 입력
	 */
	const orders = [{ id: "order-1", quantity: 2, status: "open" as const }];

	/**
	 * WHEN
	 * 표시용 주문 행 변환
	 */
	const rows = toOrderRows(orders);

	/**
	 * THEN
	 * ID와 표시 문자열의 변환 계약
	 */
	expect(rows).toStrictEqual([{ id: "order-1", quantityLabel: "2.00" }]);
});