NestJS / Editorial solution guide
Diagnose validation, guard and serialization pipeline gaps
Explain why direct tests pass while a real route accepts malformed input, bypasses policy or exposes unexpected fields.
Read-only knowledge base ยท No public comments
Recognize the symptoms
A controller unit test passes but HTTP input remains a string, a guard cannot see the expected DTO, or a response includes a field marked private. First determine which request stages actually ran. A declared type or metadata label is not evidence that its enforcing component was registered.
Understand the likely causes
- Possible causes are missing global bootstrap configuration, interfaces used where runtime classes are required, a guard expecting pipe output too early, manually constructed global components without dependencies, native response handling, or plain objects bypassing the intended serialization setup. Alternate transports may also lack the policy assumed from HTTP.
Identify the actual entry point and configuration
Record the controller/handler identifier, selected adapter, global prefix and the configuration function used by this application context. A direct method call bypasses routing, guards, pipes, interceptors and filters. A testing module does not necessarily reproduce bootstrap policies. Compare the real context with the intended registration of APP_GUARD, global pipes and serialization components, including dependency-aware construction. Do not add every missing component at once: preserve one failing synthetic request and identify the first boundary where its observed values differ from the contract. Keep private bodies and tokens out of routine traces.
Check runtime classes, values and stage order
TypeScript annotations do not parse input. For class-based ValidationPipe, supply the actual DTO class with relevant validation metadata and the intended options. For schema-based validation, register the corresponding enforcement rather than metadata alone. Guards precede parameter pipes, so a guard must not trust an already transformed DTO. Validate the raw prerequisite or move object policy to a shared service after validated input. Test missing, null, wrong type, unknown fields and well-formed unauthorized actions separately. Whitelisting a field does not grant permission to change its value.
Verify input presence without claiming a pipe test
The local patch function distinguishes an omitted summary from explicit null and an empty string. It returns a new record and keeps the original intact. This is useful evidence of domain semantics, but it does not validate incoming JSON or bind any Nest pipe. A real composed test should prove that the adapter preserves these cases and rejects invalid ones before protected effects. Do not โfixโ a pipeline gap by weakening every DTO to arbitrary strings or by turning all invalid values into an empty object that silently changes patch meaning.
function patch(old,input) {return Object.hasOwn(input,"summary") ? {...old,summary:input.summary} : {...old};}
const old={summary:"Old"};
console.assert(patch(old,{}).summary === "Old");
console.assert(patch(old,{summary:null}).summary === null);
console.assert(patch(old,{summary:""}).summary === "");
console.assert(old.summary === "Old");
All four assertions pass. They check ordinary object behavior, not DTO transformation or validation.
Trace eligibility and actual serialization
Apply object eligibility before public projection. Then check whether the result is the expected class instance or selected plain DTO and whether the configured interceptor actually receives it. Native @Res handling can alter response ownership, and multiple envelope layers can double-wrap output. Assert exact allowed keys and absence of confidential values rather than snapshotting arbitrary entities. This guide executes only the patch function; it does not run a controller, guard, serializer or HTTP adapter. Retain actual pipeline and output checks for an authorized isolated application using the pinned versions and synthetic identities.
Verify the fix
Verify a valid fixture, malformed input, unauthorized well-formed input, absent object and source failure through the intended composed context. Assert no protected call on rejection, exact public keys and safe error categories. Keep direct-call results separate from pipeline evidence.
Keep it from recurring
- Share deliberate bootstrap policy between production and tests, keep runtime DTO imports visible and document guard-versus-pipe dependencies. Retain alternate-transport policy tests and output projection fixtures. Treat metadata as a contract to enforce, not an automatic guarantee.