Node.js assert.partialDeepStrictEqual 가이드: 객체 일부만 안정적으로 검증하는 법
테스트를 작성하다 보면 객체 전체를 비교해야 할 때보다 “이 필드들이 제대로 들어왔는지만 확인하고 싶다”는 상황이 더 자주 생깁니다.
예를 들어 API 응답에는 createdAt, requestId, 내부 기본값처럼 실행할 때마다 달라지는 값이 들어갈 수 있습니다.
이때 deepStrictEqual()로 전체 객체를 고정하면 테스트가 지나치게 깨지기 쉽고, 반대로 수동으로 필드를 하나씩 검사하면 테스트 의도가 흐려집니다.
Node.js의 assert.partialDeepStrictEqual()은 이런 균형을 잡기 좋은 내장 assertion입니다.
실제 객체가 기대 객체의 구조와 값을 포함하는지 깊게 비교하되, 기대 객체에 적지 않은 추가 필드는 허용합니다.
이 글에서는 Node.js assert.partialDeepStrictEqual 기본 사용법, deepStrictEqual()과의 차이, API 응답·설정 객체·이벤트 payload 테스트 패턴, 과검증을 줄이는 실무 체크리스트를 정리합니다.
내장 테스트 러너와 함께 쓰는 흐름은 Node.js test runner 가이드: 내장 테스트 도구로 빠르게 검증하는 법을 먼저 보면 더 자연스럽습니다.
assert.partialDeepStrictEqual이 필요한 상황
H3. 객체 전체가 아니라 핵심 계약만 검증한다
서비스 코드는 시간이 지나면서 응답 필드나 내부 메타데이터가 조금씩 늘어납니다.
테스트가 객체 전체 모양을 고정하고 있으면 기능은 정상인데도 새 필드 하나 때문에 테스트가 실패할 수 있습니다.
partialDeepStrictEqual()은 테스트가 의존하는 핵심 계약만 명시하게 해 줍니다.
import assert from 'node:assert/strict';
const response = {
id: 'post_123',
title: 'Node.js 테스트 가이드',
status: 'published',
createdAt: '2026-05-18T11:00:00.000Z',
requestId: 'req_auto_generated',
};
assert.partialDeepStrictEqual(response, {
title: 'Node.js 테스트 가이드',
status: 'published',
});
이 테스트는 title과 status가 중요하다는 사실을 바로 보여 줍니다.
createdAt이나 requestId처럼 실행 환경에 따라 달라지는 값은 테스트의 관심사가 아니므로 고정하지 않습니다.
이렇게 작성하면 테스트가 덜 깨지고, 깨졌을 때도 어떤 계약이 흔들렸는지 빠르게 알 수 있습니다.
H3. 깊은 중첩 구조에서도 필요한 부분만 비교한다
부분 비교는 최상위 필드에만 유용한 것이 아닙니다. 중첩 객체 안에서도 기대 객체에 적은 필드만 깊게 확인할 수 있습니다. API 응답, 설정 병합 결과, 이벤트 payload처럼 중첩 구조가 있는 데이터에서 특히 편합니다.
import assert from 'node:assert/strict';
const config = {
server: {
host: '127.0.0.1',
port: 3000,
keepAliveTimeoutMs: 5000,
},
logging: {
level: 'info',
format: 'json',
},
};
assert.partialDeepStrictEqual(config, {
server: {
port: 3000,
},
logging: {
level: 'info',
},
});
테스트 목적이 “서버 포트와 로그 레벨 기본값이 적용됐는가”라면 이 정도 검증으로 충분합니다. 나머지 필드는 별도의 테스트에서 다루거나, 변경 가능성이 큰 구현 세부사항으로 남겨도 됩니다. 설정 객체를 만들 때 환경변수를 함께 다룬다면 Node.js loadEnvFile 가이드: 내장 기능으로 .env 설정을 관리하는 법도 같이 참고할 만합니다.
deepStrictEqual과의 차이
H3. deepStrictEqual은 완전 일치를 요구한다
assert.deepStrictEqual(actual, expected)은 실제 값과 기대 값이 깊은 구조까지 완전히 같아야 통과합니다.
반면 assert.partialDeepStrictEqual(actual, expected)은 실제 값이 기대 값을 포함하면 통과합니다.
테스트가 계약 전체를 잠그고 싶은지, 일부 핵심 필드만 확인하고 싶은지에 따라 선택이 달라집니다.
import assert from 'node:assert/strict';
const actual = {
ok: true,
data: {
id: 7,
name: 'Soya',
role: 'admin',
},
};
assert.deepStrictEqual(actual, {
ok: true,
data: {
id: 7,
name: 'Soya',
role: 'admin',
},
});
assert.partialDeepStrictEqual(actual, {
data: {
id: 7,
},
});
완전 일치가 필요한 곳도 분명히 있습니다.
예를 들어 직렬화 포맷, 공개 API 스냅샷, 보안 정책처럼 필드가 하나라도 늘거나 줄면 의미가 달라지는 경우에는 deepStrictEqual()이 더 적절합니다.
부분 비교는 “추가 필드는 허용하지만 이 값은 반드시 있어야 한다”는 의도를 표현할 때 사용하세요.
H3. 타입 비교는 strict 기준을 따른다
이름에 Strict가 들어가는 것처럼, 값 비교는 느슨한 동등성에 기대지 않습니다.
숫자 1과 문자열 '1'은 다른 값으로 처리됩니다.
테스트에서 타입까지 계약으로 보고 싶을 때 이 점이 유용합니다.
import assert from 'node:assert/strict';
const user = {
id: 1,
profile: {
active: true,
},
};
assert.partialDeepStrictEqual(user, {
id: 1,
profile: {
active: true,
},
});
입력 검증이나 API 응답 테스트에서는 타입이 흔들리는 문제가 실제 장애로 이어질 수 있습니다. 문자열로 들어온 숫자를 조용히 통과시키고 싶지 않다면 strict 비교 기반 assertion을 쓰는 편이 안전합니다. 동적 데이터 복사와 비교를 함께 다루는 테스트라면 Node.js structuredClone 가이드: 깊은 복사를 안전하게 다루는 법도 연결해서 볼 수 있습니다.
실무 테스트 패턴
H3. API 응답에서 변동 필드를 제외한다
API 테스트에서 가장 흔한 변동 필드는 시간, 추적 ID, 자동 생성 ID, 정렬과 무관한 부가 메타데이터입니다. 이 값들을 모두 고정하면 테스트가 자주 깨집니다. 핵심 상태와 사용자에게 약속한 필드만 부분 비교로 확인하면 유지보수가 쉬워집니다.
import assert from 'node:assert/strict';
import { test } from 'node:test';
async function createArticle(input) {
return {
id: `art_${Date.now()}`,
title: input.title,
status: 'draft',
audit: {
createdBy: 'system',
createdAt: new Date().toISOString(),
},
};
}
test('createArticle returns a draft article contract', async () => {
const article = await createArticle({ title: '부분 객체 테스트' });
assert.partialDeepStrictEqual(article, {
title: '부분 객체 테스트',
status: 'draft',
audit: {
createdBy: 'system',
},
});
});
이 테스트는 생성 시간이 정확히 몇 시인지에는 관심이 없습니다. 대신 “초안 상태로 생성되는가”, “감사 정보의 생성 주체가 들어가는가”처럼 비즈니스 계약에 가까운 내용을 검증합니다. 이렇게 쓰면 테스트 실패 메시지도 기능 의도에 더 가까워집니다.
H3. 이벤트 payload의 필수 필드를 검증한다
로그, 메트릭, 진단 이벤트는 운영 중 분석을 위해 필드가 늘어나는 경우가 많습니다. 이벤트 전체를 고정하면 관측성 개선 때마다 테스트를 고쳐야 합니다. 필수 필드만 부분 비교하면 이벤트 계약은 지키면서 확장은 허용할 수 있습니다.
import assert from 'node:assert/strict';
const emittedEvent = {
type: 'payment.completed',
version: 2,
payload: {
paymentId: 'pay_123',
amount: 49000,
currency: 'KRW',
},
meta: {
traceId: 'trace_generated',
publishedAt: '2026-05-18T11:00:00.000Z',
},
};
assert.partialDeepStrictEqual(emittedEvent, {
type: 'payment.completed',
payload: {
currency: 'KRW',
},
});
관측 이벤트를 설계할 때는 필수 필드와 선택 필드를 구분해 두는 것이 좋습니다. 필수 필드는 부분 비교 테스트로 고정하고, 선택 필드는 문서나 별도 케이스에서 다루면 변경 비용이 줄어듭니다. 진단 이벤트 구조를 고민 중이라면 Node.js diagnostics_channel 가이드: 라이브러리 관측 이벤트를 안전하게 발행하는 법과 함께 보면 좋습니다.
H3. 배열은 의도를 분리해서 검증한다
부분 깊은 비교를 쓸 때 배열은 특히 조심해야 합니다. 테스트 목적이 “첫 번째 항목의 핵심 필드”인지, “특정 항목이 포함되어 있는지”인지, “정렬까지 보장되는지”를 먼저 나누는 편이 좋습니다. 정렬이 중요한 테스트라면 배열 전체 또는 필요한 인덱스를 명시적으로 검증하세요.
import assert from 'node:assert/strict';
const list = [
{ id: 1, title: 'A', status: 'published' },
{ id: 2, title: 'B', status: 'draft' },
];
assert.partialDeepStrictEqual(list, [
{ id: 1, status: 'published' },
]);
const draft = list.find((item) => item.status === 'draft');
assert.partialDeepStrictEqual(draft, {
title: 'B',
});
포함 여부가 목적이라면 먼저 find()로 대상 항목을 고른 뒤 그 객체를 부분 비교하는 방식이 읽기 쉽습니다.
반대로 정렬과 개수까지 계약이라면 assert.equal(list.length, 2)와 deepStrictEqual()을 함께 쓰는 편이 더 명확합니다.
부분 비교를 만능 스냅샷처럼 쓰기보다 테스트 의도를 작게 나누는 것이 핵심입니다.
좋은 테스트로 유지하는 체크리스트
H3. 기대 객체에는 정말 중요한 필드만 넣는다
부분 비교의 장점은 테스트가 구현 세부사항에 덜 묶인다는 점입니다.
그런데 기대 객체에 모든 필드를 다시 적기 시작하면 사실상 deepStrictEqual()과 비슷한 과검증이 됩니다.
테스트를 작성할 때는 “이 필드가 바뀌면 사용자나 호출자에게 실제 문제가 되는가?”를 기준으로 고르는 편이 좋습니다.
assert.partialDeepStrictEqual(result, {
ok: true,
user: {
id: 'user_123',
plan: 'pro',
},
});
여기서 lastLoginAt, debug, cacheHit 같은 필드가 테스트 목적과 무관하다면 기대 객체에 넣지 않습니다.
테스트는 많이 검증할수록 좋은 것이 아니라, 실패했을 때 원인을 정확히 알려 줄수록 좋습니다.
짧고 의도가 선명한 assertion이 장기적으로 더 강합니다.
H3. 공개 계약과 내부 구현을 분리한다
공개 API 응답, 이벤트 스키마, 설정 파일처럼 외부와 약속한 구조는 더 엄격하게 검증할 가치가 있습니다. 반대로 내부 함수의 임시 객체나 로깅용 부가 정보는 변경 가능성이 큽니다. 두 성격을 같은 방식으로 검증하면 테스트가 불필요하게 취약해집니다.
// 공개 계약: 핵심 응답 필드는 명확히 고정
assert.partialDeepStrictEqual(publicResponse, {
status: 'ok',
data: {
id: 'post_123',
title: '테스트 전략',
},
});
// 내부 구현: 계산 결과의 핵심 의미만 확인
assert.partialDeepStrictEqual(internalState, {
cachePolicy: {
enabled: true,
},
});
공개 계약은 문서와 함께 유지하고, 내부 구현은 리팩터링 여지를 남기는 식으로 테스트 강도를 조절하세요.
이 기준이 있으면 deepStrictEqual(), partialDeepStrictEqual(), 개별 assert.equal() 중 어떤 것을 써야 할지 덜 헷갈립니다.
FAQ
H3. assert.partialDeepStrictEqual은 언제 deepStrictEqual보다 좋은가요?
객체에 변동 필드나 부가 메타데이터가 있고, 테스트 목적이 일부 핵심 필드 검증일 때 좋습니다.
API 응답의 상태값, 설정 병합 결과의 특정 옵션, 이벤트 payload의 필수 필드처럼 “반드시 포함되어야 하는 값”을 표현하기 쉽습니다.
전체 구조가 공개 계약이거나 필드 추가도 실패로 보고 싶다면 deepStrictEqual()이 더 적절합니다.
H3. 부분 비교를 쓰면 테스트가 너무 느슨해지지 않나요?
그럴 수 있습니다.
그래서 기대 객체에는 테스트가 보호해야 하는 필드를 의도적으로 골라 넣어야 합니다.
개수, 정렬, 금지 필드처럼 부분 비교만으로 드러나지 않는 조건은 assert.equal(), assert.ok(), deepStrictEqual()을 함께 써서 보완하세요.
H3. Node.js 내장 테스트 러너 없이도 쓸 수 있나요?
네.
assert.partialDeepStrictEqual()은 node:assert 모듈의 assertion이므로 node:test와 함께 쓰면 편하지만, 일반 스크립트나 다른 테스트 러너에서도 사용할 수 있습니다.
다만 팀 프로젝트에서는 한 가지 테스트 실행 방식을 정해 두는 편이 실패 로그와 CI 구성이 단순해집니다.
마무리
assert.partialDeepStrictEqual()은 테스트를 덜 깨지게 만들기 위한 “느슨한 비교”가 아니라, 중요한 계약만 선명하게 고정하기 위한 도구입니다.
객체 전체를 잠글 필요가 없을 때 핵심 필드만 깊게 검증하면 테스트 의도가 더 잘 드러나고 유지보수 비용도 줄어듭니다.
실무에서는 공개 계약은 엄격하게, 변동이 큰 내부 데이터는 부분 비교로 유연하게 다루는 균형이 중요합니다. 내장 테스트 러너, 진단 이벤트, 설정 객체 테스트와 함께 사용하면 의존성을 늘리지 않고도 꽤 단단한 Node.js 테스트 구조를 만들 수 있습니다.
함께 읽기
이 글은 AI가 작성/정리하고, 오너가 방향을 결정한 프로젝트 운영 로그입니다.