내장 bun:test 모듈에서 가져온 Jest 유사 API 로 테스트를 정의합니다. 장기적으로 Bun 은 완전한 Jest 호환성을 목표로 합니다. 현재는 제한된 세트의 expect 매처만 지원됩니다.
기본 사용
간단한 테스트를 정의하려면.
import { expect, test } from "bun:test";
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});테스트 그룹화
테스트는 describe 로 스위트에 그룹화할 수 있습니다.
import { expect, test, describe } from "bun:test";
describe("arithmetic", () => {
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});
test("2 * 2", () => {
expect(2 * 2).toBe(4);
});
});비동기 테스트
테스트는 비동기일 수 있습니다.
import { expect, test } from "bun:test";
test("2 * 2", async () => {
const result = await Promise.resolve(2 * 2);
expect(result).toEqual(4);
});또는 완료 신호를 보내기 위해 done 콜백을 사용할 수 있습니다. 테스트 정의에서 매개변수로 done 콜백을 포함하면 호출해야 하며 그렇지 않으면 테스트가 중단됩니다.
import { expect, test } from "bun:test";
test("2 * 2", done => {
Promise.resolve(2 * 2).then(result => {
expect(result).toEqual(4);
done();
});
});타임아웃
test 에 세 번째 인수로 숫자를 전달하여 테스트당 타임아웃을 밀리초 단위로 선택적으로 지정할 수 있습니다.
import { test } from "bun:test";
test("wat", async () => {
const data = await slowOperation();
expect(data).toBe(42);
}, 500); // 테스트는 500ms 미만으로 실행되어야 함bun:test 에서 테스트 타임아웃은 잡을 수 없는 예외를 던져 테스트가 중지되고 실패하도록 강제합니다. 또한 좀비 프로세스가 백그라운드에서 숨어있는 것을 방지하기 위해 테스트에서 생성된 모든 하위 프로세스도 종료합니다.
각 테스트의 기본 타임아웃은 이 타임아웃 옵션이나 jest.setDefaultTimeout() 으로 재정의되지 않는 한 5000ms(5 초) 입니다.
재시도 및 반복
test.retry
retry 옵션을 사용하여 테스트가 실패하면 자동으로 재시도하세요. 테스트는 지정된 시도 횟수 내에 성공하면 통과합니다. 이는 간헐적으로 실패할 수 있는 flaky 테스트에 유용합니다.
import { test } from "bun:test";
test(
"flaky 네트워크 요청",
async () => {
const response = await fetch("https://example.com/api");
expect(response.ok).toBe(true);
},
{ retry: 3 }, // 테스트가 실패하면 최대 3 회 재시도
);test.repeats
repeats 옵션을 사용하여 통과/실패 상태와 관계없이 테스트를 여러 번 실행하세요. 반복 중 어느 하나라도 실패하면 테스트는 실패합니다. 이는 flaky 테스트를 감지하거나 스트레스 테스트에 유용합니다. repeats: N 은 테스트를 총 N+1 회 실행합니다 (초기 1 회 + N 회 반복).
import { test } from "bun:test";
test(
"테스트가 안정적임을 확인",
() => {
expect(Math.random()).toBeLessThan(1);
},
{ repeats: 20 }, // 총 21 회 실행 (초기 1 회 + 20 회 반복)
);NOTE
동일한 테스트에서 `retry` 와 `repeats` 를 모두 사용할 수 없습니다.🧟 좀비 프로세스 킬러
테스트가 타임아웃되고 Bun.spawn, Bun.spawnSync 또는 node:child_process 를 통해 테스트에서 생성된 프로세스가 종료되지 않으면 자동으로 종료되고 콘솔에 메시지가 기록됩니다. 이는 타임아웃된 테스트 후 백그라운드에서 좀비 프로세스가 남아있는 것을 방지합니다.
테스트 한정자
test.skip
test.skip 으로 개별 테스트를 건너뛰세요. 이러한 테스트는 실행되지 않습니다.
import { expect, test } from "bun:test";
test.skip("wat", () => {
// TODO: 수정 필요
expect(0.1 + 0.2).toEqual(0.3);
});test.todo
test.todo 로 테스트를 todo 로 표시하세요. 이러한 테스트는 실행되지 않습니다.
import { expect, test } from "bun:test";
test.todo("fix this", () => {
myTestFunction();
});todo 테스트를 실행하고 통과하는 테스트를 찾으려면 bun test --todo 를 사용하세요.
bun test --todomy.test.ts:
✗ unimplemented feature
^ 이 테스트는 todo 로 표시되었지만 통과합니다. `.todo` 를 제거하거나 테스트가 올바른지 확인하세요.
0 pass
1 fail
1 expect() calls이 플래그를 사용하면 실패하는 todo 테스트는 오류를 발생시키지 않지만 통과하는 todo 테스트는 실패로 표시되어 todo 표시를 제거하거나 테스트를 수정할 수 있습니다.
test.only
특정 테스트나 스위트만 실행하려면 test.only() 또는 describe.only() 를 사용하세요.
import { test, describe } from "bun:test";
test("test #1", () => {
// 실행되지 않음
});
test.only("test #2", () => {
// 실행됨
});
describe.only("only", () => {
test("test #3", () => {
// 실행됨
});
});다음 명령은 테스트 #2 와 #3 만 실행합니다.
bun test --only다음 명령은 테스트 #1, #2, #3 만 실행합니다.
bun testtest.if
테스트를 조건부로 실행하려면 test.if() 를 사용하세요. 조건이 참이면 테스트가 실행됩니다. 이는 특정 아키텍처나 운영 체제에서만 실행되어야 하는 테스트에 특히 유용합니다.
test.if(Math.random() > 0.5)("절반의 시간에 실행", () => {
// ...
});
const macOS = process.platform === "darwin";
test.if(macOS)("macOS 에서 실행", () => {
// macOS 이면 실행
});test.skipIf
대신 일부 조건에 따라 테스트를 건너뛰려면 test.skipIf() 또는 describe.skipIf() 를 사용하세요.
const macOS = process.platform === "darwin";
test.skipIf(macOS)("macOS 가 아닌 곳에서 실행", () => {
// macOS 가 아니면 실행
});test.todoIf
대신 테스트를 TODO 로 표시하려면 test.todoIf() 또는 describe.todoIf() 를 사용하세요. skipIf 또는 todoIf 를 신중하게 선택하면 예를 들어 "이 대상에 대해 무효"와 "아직 계획되었지만 구현되지 않음"의 차이를 보여줄 수 있습니다.
const macOS = process.platform === "darwin";
// TODO: 아직 Linux 에 대해서만 구현했습니다.
test.todoIf(macOS)("posix 에서 실행", () => {
// macOS 가 아니면 실행
});test.failing
테스트가 현재 실패하고 있지만 추적하고 통과하기 시작할 때 알림을 받으려면 test.failing() 을 사용하세요. 이는 테스트 결과를 반전합니다.
.failing()으로 표시된 실패하는 테스트는 통과합니다.failing()으로 표시된 통과하는 테스트는 실패합니다 (이제 통과하므로 수정해야 한다는 메시지와 함께)
// 테스트가 예상대로 실패하므로 통과합니다
test.failing("math 가 고장남", () => {
expect(0.1 + 0.2).toBe(0.3); // 부동 소수점 정밀도로 인해 실패
});
// 이제 통과한다는 메시지와 함께 실패합니다
test.failing("수정된 버그", () => {
expect(1 + 1).toBe(2); // 통과하지만 실패할 것으로 예상됨
});이는 나중에 수정할 계획인 알려진 버그를 추적하거나 테스트 주도 개발을 구현하는 데 유용합니다.
Describe 블록의 조건부 테스트
조건부 한정자 .if(), .skipIf() 및 .todoIf() 는 스위트 내의 모든 테스트에 영향을 미치는 describe 블록에도 적용할 수 있습니다.
const isMacOS = process.platform === "darwin";
// macOS 에서만 전체 스위트 실행
describe.if(isMacOS)("macOS 특정 기능", () => {
test("기능 A", () => {
// macOS 에서만 실행
});
test("기능 B", () => {
// macOS 에서만 실행
});
});
// Windows 에서 전체 스위트 건너뛰기
describe.skipIf(process.platform === "win32")("Unix 기능", () => {
test("기능 C", () => {
// Windows 에서 건너뜀
});
});
// Linux 에서 전체 스위트를 TODO 로 표시
describe.todoIf(process.platform === "linux")("다가오는 Linux 지원", () => {
test("기능 D", () => {
// Linux 에서 TODO 로 표시
});
});매개변수화된 테스트
test.each 및 describe.each
여러 데이터 세트로 동일한 테스트를 실행하려면 test.each 를 사용하세요. 이는 제공된 각 테스트 케이스마다 한 번씩 실행되는 매개변수화된 테스트를 생성합니다.
const cases = [
[1, 2, 3],
[3, 4, 7],
];
test.each(cases)("%p + %p should be %p", (a, b, expected) => {
expect(a + b).toBe(expected);
});describe.each 를 사용하여 각 테스트 케이스마다 한 번씩 실행되는 매개변수화된 스위트를 생성할 수도 있습니다.
describe.each([
[1, 2, 3],
[3, 4, 7],
])("add(%i, %i)", (a, b, expected) => {
test(`${expected} 반환`, () => {
expect(a + b).toBe(expected);
});
test(`합이 각 값보다 큼`, () => {
expect(a + b).toBeGreaterThan(a);
expect(a + b).toBeGreaterThan(b);
});
});인수 전달
인수가 테스트 함수에 전달되는 방식은 테스트 케이스의 구조에 따라 다릅니다.
- 테이블 행이 배열인 경우 (예:
[1, 2, 3]) 각 요소는 개별 인수로 전달됩니다 - 행이 배열이 아닌 경우 (예: 객체) 단일 인수로 전달됩니다
// 배열 항목이 개별 인수로 전달됨
test.each([
[1, 2, 3],
[4, 5, 9],
])("add(%i, %i) = %i", (a, b, expected) => {
expect(a + b).toBe(expected);
});
// 객체 항목이 단일 인수로 전달됨
test.each([
{ a: 1, b: 2, expected: 3 },
{ a: 4, b: 5, expected: 9 },
])("add($a, $b) = $expected", data => {
expect(data.a + data.b).toBe(data.expected);
});형식 지정자
테스트 제목을 포맷팅하기 위해 사용할 수 있는 여러 옵션이 있습니다.
| 지정자 | 설명 |
|---|---|
%p | pretty-format |
%s | 문자열 |
%d | 숫자 |
%i | 정수 |
%f | 부동 소수점 |
%j | JSON |
%o | 객체 |
%# | 테스트 케이스 인덱스 |
%% | 단일 퍼센트 기호 (%) |
예시
// 기본 지정자
test.each([
["hello", 123],
["world", 456],
])("string: %s, number: %i", (str, num) => {
// "string: hello, number: 123"
// "string: world, number: 456"
});
// pretty-format 출력을 위한 %p
test.each([
[{ name: "Alice" }, { a: 1, b: 2 }],
[{ name: "Bob" }, { x: 5, y: 10 }],
])("user %p with data %p", (user, data) => {
// "user { name: 'Alice' } with data { a: 1, b: 2 }"
// "user { name: 'Bob' } with data { x: 5, y: 10 }"
});
// 인덱스를 위한 %#
test.each(["apple", "banana"])("fruit #%# is %s", fruit => {
// "fruit #0 is apple"
// "fruit #1 is banana"
});단언 카운팅
Bun 은 테스트 중에 특정 수의 단언이 호출되었는지 확인하는 것을 지원합니다.
expect.hasAssertions()
expect.hasAssertions() 을 사용하여 테스트 중에 최소한 하나의 단언이 호출되었는지 확인하세요.
test("비동기 작업이 단언 호출", async () => {
expect.hasAssertions(); // 단언이 호출되지 않으면 실패
const data = await fetchData();
expect(data).toBeDefined();
});이는 단언이 실제로 실행되는지 확인하기 위해 비동기 테스트에 특히 유용합니다.
expect.assertions(count)
expect.assertions(count) 를 사용하여 테스트 중에 특정 수의 단언이 호출되었는지 확인하세요.
test("정확히 2 개의 단언", () => {
expect.assertions(2); // 정확히 2 개의 단언이 호출되지 않으면 실패
expect(1 + 1).toBe(2);
expect("hello").toContain("ell");
});이는 여러 코드 경로가 있는 복잡한 비동기 코드에서 모든 단언이 실행되는지 확인하는 데 도움이 됩니다.
타입 테스트
Bun 은 Vitest 와 호환되는 타입 테스트를 위한 expectTypeOf 를 포함합니다.
expectTypeOf
expectTypeOf 함수는 TypeScript 타입 체커에 의해 확인되는 타입 수준 단언을 제공합니다. 타입을 테스트하려면.
expectTypeOf를 사용하여 타입 단언 작성bunx tsc --noEmit을 실행하여 타입이 올바른지 확인
import { expectTypeOf } from "bun:test";
// 기본 타입 단언
expectTypeOf<string>().toEqualTypeOf<string>();
expectTypeOf(123).toBeNumber();
expectTypeOf("hello").toBeString();
// 객체 타입 매칭
expectTypeOf({ a: 1, b: "hello" }).toMatchObjectType<{ a: number }>();
// 함수 타입
function greet(name: string): string {
return `Hello ${name}`;
}
expectTypeOf(greet).toBeFunction();
expectTypeOf(greet).parameters.toEqualTypeOf<[string]>();
expectTypeOf(greet).returns.toEqualTypeOf<string>();
// 배열 타입
expectTypeOf([1, 2, 3]).items.toBeNumber();
// Promise 타입
expectTypeOf(Promise.resolve(42)).resolves.toBeNumber();expectTypeOf 매처에 대한 전체 문서는 API 참조 를 참조하세요.
매처
Bun 은 다음 매처를 구현합니다. 완전한 Jest 호환성은 로드맵에 있습니다. 진행 상황 추적.
기본 매처
| 상태 | 매처 |
|---|---|
| ✅ | .not |
| ✅ | .toBe() |
| ✅ | .toEqual() |
| ✅ | .toBeNull() |
| ✅ | .toBeUndefined() |
| ✅ | .toBeNaN() |
| ✅ | .toBeDefined() |
| ✅ | .toBeFalsy() |
| ✅ | .toBeTruthy() |
| ✅ | .toStrictEqual() |
문자열 및 배열 매처
| 상태 | 매처 |
|---|---|
| ✅ | .toContain() |
| ✅ | .toHaveLength() |
| ✅ | .toMatch() |
| ✅ | .toContainEqual() |
| ✅ | .stringContaining() |
| ✅ | .stringMatching() |
| ✅ | .arrayContaining() |
객체 매처
| 상태 | 매처 |
|---|---|
| ✅ | .toHaveProperty() |
| ✅ | .toMatchObject() |
| ✅ | .toContainAllKeys() |
| ✅ | .toContainValue() |
| ✅ | .toContainValues() |
| ✅ | .toContainAllValues() |
| ✅ | .toContainAnyValues() |
| ✅ | .objectContaining() |
숫자 매처
| 상태 | 매처 |
|---|---|
| ✅ | .toBeCloseTo() |
| ✅ | .closeTo() |
| ✅ | .toBeGreaterThan() |
| ✅ | .toBeGreaterThanOrEqual() |
| ✅ | .toBeLessThan() |
| ✅ | .toBeLessThanOrEqual() |
함수 및 클래스 매처
| 상태 | 매처 |
|---|---|
| ✅ | .toThrow() |
| ✅ | .toBeInstanceOf() |
Promise 매처
| 상태 | 매처 |
|---|---|
| ✅ | .resolves() |
| ✅ | .rejects() |
목 함수 매처
| 상태 | 매처 |
|---|---|
| ✅ | .toHaveBeenCalled() |
| ✅ | .toHaveBeenCalledTimes() |
| ✅ | .toHaveBeenCalledWith() |
| ✅ | .toHaveBeenLastCalledWith() |
| ✅ | .toHaveBeenNthCalledWith() |
| ✅ | .toHaveReturned() |
| ✅ | .toHaveReturnedTimes() |
| ✅ | .toHaveReturnedWith() |
| ✅ | .toHaveLastReturnedWith() |
| ✅ | .toHaveNthReturnedWith() |
스냅샷 매처
| 상태 | 매처 |
|---|---|
| ✅ | .toMatchSnapshot() |
| ✅ | .toMatchInlineSnapshot() |
| ✅ | .toThrowErrorMatchingSnapshot() |
| ✅ | .toThrowErrorMatchingInlineSnapshot() |
유틸리티 매처
| 상태 | 매처 |
|---|---|
| ✅ | .extend |
| ✅ | .anything() |
| ✅ | .any() |
| ✅ | .assertions() |
| ✅ | .hasAssertions() |
아직 구현되지 않음
| 상태 | 매처 |
|---|---|
| ❌ | .addSnapshotSerializer() |
모범 사례
설명적인 테스트 이름 사용
// 좋음
test("세금을 포함한 총 가격 계산을 여러 항목에 대해 수행", () => {
// 테스트 구현
});
// 피하기
test("가격 계산", () => {
// 테스트 구현
});관련 테스트 그룹화
describe("사용자 인증", () => {
describe("유효한 자격 증명", () => {
test("사용자 데이터 반환", () => {
// 테스트 구현
});
test("인증 토큰 설정", () => {
// 테스트 구현
});
});
describe("유효하지 않은 자격 증명", () => {
test("인증 오류 발생", () => {
// 테스트 구현
});
});
});적절한 매처 사용
// 좋음: 특정 매처 사용
expect(users).toHaveLength(3);
expect(user.email).toContain("@");
expect(response.status).toBeGreaterThanOrEqual(200);
// 피하기: 모든 것에 toBe 사용
expect(users.length === 3).toBe(true);
expect(user.email.includes("@")).toBe(true);
expect(response.status >= 200).toBe(true);오류 조건 테스트
test("유효하지 않은 입력에 대해 오류 발생", () => {
expect(() => {
validateEmail("not-an-email");
}).toThrow("잘못된 이메일 형식");
});
test("비동기 오류 처리", async () => {
await expect(async () => {
await fetchUser("invalid-id");
}).rejects.toThrow("사용자를 찾을 수 없음");
});설정 및 정리 사용
import { beforeEach, afterEach, test } from "bun:test";
let testUser;
beforeEach(() => {
testUser = createTestUser();
});
afterEach(() => {
cleanupTestUser(testUser);
});
test("사용자 프로필 업데이트", () => {
// 테스트에서 testUser 사용
});