Jest, Mocha, node:test 등 어떤 테스트 프레임워크를 사용했든, 모두 간단한 아이디어 위에 구축되어 있습니다. 즉, 기대하는 바를 명시하고, 현실이 그 기대와 다르면 오류를 발생시키는 것입니다. Node.js는 이 아이디어를 assert라는 내장 모듈로 제공합니다. 설치도, 의존성도 필요 없이, 그저 require하고 가정 검증을 시작할 수 있습니다.
assert 모듈은 그 자체로 알아둘 가치가 있습니다. 스크립트에서 빠른 건전성 검사를 가능하게 하고, 많은 테스트 러너의 기반이 되며, 어떤 프레임워크가 이를 꾸미기 전에 단언(assertion)이 실제로 무엇인지 가르쳐줍니다. 이 가이드에서는 중요한 메서드, 사람들을 헷갈리게 하는 strict-vs-legacy 구분, 오류 및 비동기 코드에서 단언하는 방법, 그리고 동일한 모듈이 API 응답을 검증하는 데 어떻게 도움이 되는지 다룹니다.
assert 모듈의 역할
단언(assertion)은 프로그램이 올바르다고 간주되려면 반드시 참이어야 하는 문(statement)입니다. assert.strictEqual(total, 100)을 작성할 때, 당신은 total이 100과 같아야 한다고 선언하는 것입니다. 만약 같다면, 아무 일도 일어나지 않고 실행은 계속됩니다. 만약 같지 않다면, assert는 AssertionError를 발생시켜 실행을 중단시키고 무엇이 잘못되었는지 알려줍니다.
모듈을 가져옵니다. 현대적이고 권장되는 형식은 strict 버전입니다:
const assert = require('node:assert/strict');
// 또는 ES 모듈의 경우:
// import assert from 'node:assert/strict';
가장 간단한 단언은 값이 truthy인지 확인합니다:
const user = getUser(42);
assert(user, 'getUser should return a user object');
첫 번째 인수는 테스트 중인 값입니다. 두 번째 인수는 선택 사항이며, 단언이 실패할 때 표시되는 메시지입니다. 항상 이 메시지를 작성하세요. "getUser should return a user object"라고 말하는 실패는 bare AssertionError보다 훨씬 유용합니다. 단언을 이해하는 것은 전용 API 단언으로 테스트 도구로 넘어갈 때도 도움이 됩니다.
Strict 모드 대 Legacy 모드
이것은 제대로 이해해야 할 가장 중요한 단일 사항입니다. assert 모듈에는 두 가지 모드가 있습니다.
require('node:assert')에서 얻는 Legacy 모드는 assert.equal 및 assert.deepEqual에 대해 느슨한 동등성(==)을 사용합니다. 이는 assert.equal(1, '1')이 통과한다는 의미입니다. 왜냐하면 JavaScript에서 1 == '1'은 참이기 때문입니다. 느슨한 동등성은 잘 알려진 버그의 원천입니다.
require('node:assert/strict')에서 얻는 Strict 모드는 모든 것에 대해 엄격한 동등성(===)을 사용합니다. assert.equal(1, '1')은 유형이 다르기 때문에 실패하며, 이는 올바른 동작입니다.
const looseAssert = require('node:assert');
looseAssert.equal(1, '1'); // 통과, 놀랍고 위험함
const strict = require('node:assert/strict');
strict.equal(1, '1'); // AssertionError 발생, 올바름
Strict 모드를 사용하세요. 테스트에서 느슨한 동등성을 받아들일 좋은 이유는 없습니다. 이 가이드의 나머지 부분은 node:assert/strict를 가정하며, 여기서 assert.equal은 assert.strictEqual처럼 동작합니다.
값 비교: equal 및 strictEqual
assert.strictEqual(actual, expected)는 두 값이 ===로 동일한지 확인합니다. 이는 숫자, 문자열, 불리언과 같은 원시 타입에 대한 핵심 작업입니다.
const assert = require('node:assert/strict');
function priceWithTax(price, rate) {
return price + price * rate;
}
assert.strictEqual(priceWithTax(100, 0.08), 108, '세금 계산은 8%를 추가해야 합니다');
assert.strictEqual(typeof priceWithTax(100, 0.08), 'number', '결과는 숫자여야 합니다');
두 값이 동일하지 않을 때 통과하는 assert.notStrictEqual도 있습니다. 값이 변경되었음을 확인하는 데 사용하세요:
const before = getCacheKey();
refreshCache();
const after = getCacheKey();
assert.notStrictEqual(before, after, '캐시 키는 새로 고침 후 변경되어야 합니다');
객체와 배열의 경우 strictEqual은 도움이 되지 않습니다. 동일한 내용을 가진 두 객체 리터럴은 다른 참조이므로 ===는 false를 반환합니다. 이것이 깊은 동등성이 필요한 이유입니다.
객체 비교: deepStrictEqual
assert.deepStrictEqual(actual, expected)는 구조와 값을 재귀적으로 비교합니다. 두 객체는 모든 키가 엄격히 동일한 값을 가질 때, 즉 모든 깊이에서 일치할 때 통과합니다.
const assert = require('node:assert/strict');
function buildOrder(id, items) {
return { id, items, status: 'pending' };
}
assert.deepStrictEqual(
buildOrder(7, ['keyboard', 'mouse']),
{ id: 7, items: ['keyboard', 'mouse'], status: 'pending' },
'주문 객체는 예상되는 형태와 일치해야 합니다'
);
이 메서드는 객체를 반환하는 함수를 테스트할 때, 특히 JSON 본문이 객체이므로 API 응답을 테스트할 때 가장 많이 사용하게 될 것입니다. 이의 반대인 assert.notDeepStrictEqual은 구조가 다를 때 통과합니다.
주의할 점은 deepStrictEqual도 타입을 확인한다는 것입니다. 숫자 7을 포함하는 속성은 문자열 '7'을 포함하는 속성과 일치하지 않습니다. 이러한 엄격함은 기능입니다. 이는 데이터의 타입 변화를 잡아냅니다. 여러 입력 조합으로 함수를 테스트하는 경우, CSV 및 JSON을 사용한 데이터 기반 테스트 가이드는 데이터 세트 전반에 걸쳐 단언을 확장하는 방법을 보여줍니다.
deepStrictEqual이 실패하면 Node는 어떤 속성이 일치하지 않는지 정확히 강조하는 차이점(diff)을 출력합니다. 이 차이점은 모듈의 가장 유용한 기능 중 하나입니다. 두 개의 큰 객체를 쳐다보며 차이점을 찾으려고 애쓰는 대신, 몇 줄의 강조된 내용을 읽으면 됩니다. 중첩된 데이터의 경우, 그것만으로도 각 필드를 strictEqual로 수동으로 확인하는 것보다 deepStrictEqual을 사용하는 것을 정당화합니다.
assert.match 및 assert.ok를 사용한 부분 일치
모든 검사가 완전한 동등성을 필요로 하는 것은 아닙니다. 때로는 값이 대략적으로 올바른지 여부만 신경 쓰는 경우가 있습니다. assert.ok(value)는 값이 truthy일 때마다 통과하며, "이것은 존재해야 한다"는 검사에 적합한 도구입니다. 예를 들어 비어 있지 않은 문자열, 정의된 객체, 0이 아닌 개수 등이 있습니다.
assert.match(string, regexp)는 문자열이 정규 표현식과 일치하는지 확인하고, assert.doesNotMatch는 그 반대를 확인합니다. 이들은 정확한 내용은 다양하지만 형태는 고정된 값에 유용합니다.
const assert = require('node:assert/strict');
function generateOrderId() {
return 'ORD-' + Date.now();
}
const id = generateOrderId();
// 정확한 타임스탬프는 변경되므로, 값 대신 형식을 단언합니다
assert.match(id, /^ORD-\d+$/, '주문 ID는 ORD- 다음에 숫자가 와야 합니다');
assert.ok(id.length > 4, '주문 ID는 비어 있지 않아야 합니다');
이 패턴은 특히 API 테스트에서 중요합니다. 응답에 타임스탬프, 생성된 ID, 예측할 수 없는 토큰이 포함될 수 있기 때문입니다. match로 형식을 단언하고 ok로 존재 여부를 단언하며, strictEqual은 실제로 제어하는 값에만 사용합니다.
코드가 오류를 발생시키는지 단언하기
때로는 올바른 동작이 오류를 발생시키는 것을 의미하기도 합니다. assert.throws(fn, expectedError)는 함수를 실행하고, 오류가 발생할 때만 통과합니다.
const assert = require('node:assert/strict');
function parsePort(value) {
const port = Number(value);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new RangeError('포트는 1에서 65535 사이의 정수여야 합니다');
}
return port;
}
// 통과: 잘못된 입력은 RangeError를 발생시켜야 합니다
assert.throws(
() => parsePort('70000'),
RangeError,
'범위를 벗어난 포트는 RangeError를 발생시켜야 합니다'
);
// 정규식으로 오류 메시지를 일치시킬 수도 있습니다
assert.throws(
() => parsePort('abc'),
/must be an integer/,
'숫자가 아닌 포트는 설명적인 오류를 발생시켜야 합니다'
);
// 통과: 유효한 입력은 오류를 발생시키지 않아야 합니다
assert.doesNotThrow(
() => parsePort('8080'),
'유효한 포트는 오류를 발생시키지 않아야 합니다'
);
함수가 아닌 호출을 전달한다는 점에 유의하세요. assert.throws(parsePort('70000'))는 assert가 이를 보기 전에 parsePort를 실행하여 오류가 잡히지 않고 빠져나갈 것입니다. 항상 () => parsePort('70000')처럼 래핑하세요.
비동기 코드에 대한 단언: rejects
현대 Node.js 코드는 Promise로 가득하며, 거부된 Promise는 오류가 발생한 비동기식과 같습니다. assert.rejects 및 assert.doesNotReject는 이 경우를 처리합니다. 둘 다 Promise를 반환하므로, await를 사용해야 합니다.
const assert = require('node:assert/strict');
async function fetchUser(id) {
if (typeof id !== 'number') {
throw new TypeError('id는 숫자여야 합니다');
}
// ... 실제 조회는 여기에 들어갑니다
return { id, name: 'Dana Lee' };
}
async function runTests() {
// 통과: 잘못된 입력은 TypeError와 함께 거부됩니다
await assert.rejects(
fetchUser('not-a-number'),
TypeError,
'문자열 id는 거부되어야 합니다'
);
// 통과: 좋은 입력은 거부되지 않고 해결됩니다
await assert.doesNotReject(
fetchUser(101),
'유효한 id는 깨끗하게 해결되어야 합니다'
);
}
runTests();
assert.rejects 호출에 await를 빼먹는 것은 흔한 실수입니다. await가 없으면 테스트 함수는 단언이 완료되기 전에 끝나고, 실패는 명확한 테스트 오류 대신 처리되지 않은 거부가 됩니다.
assert를 사용하여 API 응답 테스트하기
assert 모듈은 HTTP 요청의 출력을 확인하는 데 정말 유용합니다. 엔드포인트를 가져온 후에는 상태 코드와 JSON 본문이 있으며, 둘 다 단언할 수 있는 대상입니다.
const assert = require('node:assert/strict');
async function testGetUser() {
const res = await fetch('https://api.example.com/users/101');
// 상태 코드 확인
assert.strictEqual(res.status, 200, 'GET /users/101은 200을 반환해야 합니다');
const body = await res.json();
// 응답의 형태와 타입 확인
assert.strictEqual(typeof body.id, 'number', 'id는 숫자여야 합니다');
assert.strictEqual(body.id, 101, 'id는 요청된 사용자와 일치해야 합니다');
assert.ok(body.name, '응답에 이름이 포함되어야 합니다');
assert.ok(Array.isArray(body.roles), 'roles는 배열이어야 합니다');
}
testGetUser().then(
() => console.log('API 테스트 통과'),
(err) => { console.error('API 테스트 실패:', err.message); process.exitCode = 1; }
);
이 패턴은 작동하며, 기본 사항을 가르쳐줍니다. 그러나 실제 API 테스트 스위트의 경우, 원시 assert가 제공하지 않는 구조화된 실행, 재시도, 환경 처리 및 보고가 필요할 것입니다. 자동화된 테스트 스크립트 작성 방법 및 pytest API 자동화 프레임워크에 대한 저희 가이드는 다음 단계를 다룹니다.
테스트 하네스를 직접 구축하고 싶지 않다면, Apidog를 사용하면 상태 코드, 헤더, JSON 필드에 대한 시각적 단언을 작성 코드 없이 API 요청을 정의하고 연결할 수 있습니다. 요청을 자동화된 테스트 시나리오로 연결하고, CI/CD에서 실행할 수 있으며, assert가 제공하는 제어가 필요할 때 여전히 사용자 정의 스크립트에 들어갈 수 있습니다. 이는 "엔드포인트가 있다"에서 "테스트 스위트가 있다"로 가는 더 빠른 경로이며, Apidog를 다운로드하여 무료로 사용할 수 있습니다. Apidog와 함께 Node.js assert 모듈은 빠른 검사와 완전한 스위트 모두를 다룹니다.
자주 묻는 질문
assert 모듈을 설치해야 하나요?
아니요. assert는 Node.js에 내장되어 있습니다. require('node:assert/strict') 또는 require('node:assert')로 가져오세요. 설치할 npm 패키지나 관리할 의존성이 없습니다. node: 접두사는 핵심 모듈을 로드하고 있음을 명시합니다.
assert.equal과 assert.strictEqual의 차이점은 무엇인가요?
Legacy 모드에서 assert.equal은 느슨한 동등성(==)을 사용하고 assert.strictEqual은 엄격한 동등성(===)을 사용합니다. node:assert/strict를 통해 로드되는 strict 모드에서는 assert.equal이 assert.strictEqual과 동일하게 동작합니다. 타입 강제가 테스트를 조용히 통과하게 하지 않도록 항상 strict 모드를 사용하세요.
strictEqual 대신 deepStrictEqual은 언제 사용해야 하나요?
숫자, 문자열, 불리언과 같은 원시 타입에는 strictEqual을 사용하세요. 객체와 배열에는 deepStrictEqual을 사용하세요. strictEqual은 참조를 비교하며, 두 개의 별개 객체는 결코 참조 동등하지 않기 때문입니다. JSON 본문이나 반환된 객체에 대해 단언할 때마다 deepStrictEqual을 사용하세요.
assert 모듈을 사용해야 하나요, 아니면 Jest와 같은 테스트 프레임워크를 사용해야 하나요?
빠른 스크립트 작성 및 학습용으로는 원시 assert가 좋습니다. 실제 테스트 스위트의 경우 프레임워크를 사용하세요. Node.js는 또한 node:test라는 내장 테스트 러너를 제공하며, 이는 assert와 자연스럽게 결합되어 외부 의존성 없이 테스트 구성, 보고 및 병렬 처리를 제공합니다.
비동기 함수가 거부되는 것을 어떻게 단언하나요?
assert.rejects를 사용하고, await를 붙이는 것을 잊지 마세요. Promise 또는 비동기 함수 호출을 전달하고, 선택적으로 예상되는 오류 유형 또는 메시지 정규식을 추가할 수 있습니다. assert.rejects(somePromise, TypeError)는 Promise가 TypeError와 함께 거부될 때만 통과합니다. await가 없으면, 실패는 명확한 단언 오류 대신 처리되지 않은 거부가 됩니다.
