NestJS / Intermediate: policies, adapters and sources / Lesson 10
Testing overrides and composed request contracts
Separate pure domain evidence, container compilation and real request-pipeline coverage.
On this page
Lesson goals
- Replace provider tokens with deterministic fakes without contacting configured sources.
- Assert identities and effects in addition to status or absence of exceptions.
Choose the test boundary before choosing the tool
A direct service test can prove domain rules without Nest. A compiled testing module can verify token wiring and module visibility with fakes, while a composed application test adds guards, pipes, interceptors and filters. HTTP integration additionally checks adapter serialization and headers. These are different claims. Avoid starting a database merely to test a local policy predicate, and avoid calling a direct controller method a complete authorization test. The test runner is another versioned choice; current Nest guidance keeps testing utilities runner-agnostic while generated defaults differ by project/module setup. Record the actual environment rather than assuming one global default.
Override the exact provider token before compilation
The fragment builds a testing module from the owner module and overrides its reader token with a synthetic value. It assumes imports and a fake matching the actual domain contract. A fake with the wrong methods can make construction succeed yet fail on use. Override before compile, and do not let a real asynchronous factory connect to its configured service merely because the test never intends to call it. Container construction can itself have effects. This fragment is non-runnable and was not compiled; it describes the verification a real isolated project test should perform.
const moduleRef = await Test.createTestingModule({
imports:[CatalogModule],
})
.overrideProvider(COURSE_READER)
.useValue(fakeReader)
.compile();
const service = moduleRef.get(CatalogService);
Test, tokens, classes and fakeReader are assumed. No Nest context or real provider factory was created here.
Derive negative cases from the use case
For a course read, pair visible and denied principals against the same ID, then add absent data, malformed input and a source failure. For a write, assert no call after rejection, expected revision conflicts and response loss after commit. Serialization needs exact public keys; scoped providers need isolation; caches need identity and expiry cases. Do not copy a generic status checklist across every lesson. A denied HTTP response does not prove a write never occurred, and a 200 result does not prove it belongs to the correct tenant. Assert the effect and identity that make the outcome meaningful.
Prove denied work never reaches the source
The local test injects a reader spy into a plain function. The cross-tenant principal receives denial with zero calls, while the allowed principal produces one call for the expected tenant. It tests policy placement, not token override or guard binding. A real composed route should preserve the same invariant through the actual pipeline. Keep clocks and records synthetic and deterministic. Clean up any application context owned by future integration tests; a test that passes its assertions but leaves resources open is not a well-owned verification lifecycle.
function read(principal,tenant,reader) {
if (principal.tenantId !== tenant) return {code:"DENIED"};
return reader.find(tenant);
}
const calls=[]; const reader={find:tenant => {calls.push(tenant);return {id:"c1"};}};
console.assert(read({tenantId:"lab-b"},"lab-a",reader).code === "DENIED");
console.assert(calls.length === 0);
console.assert(read({tenantId:"lab-a"},"lab-a",reader).id === "c1");
console.assert(JSON.stringify(calls) === '["lab-a"]');
The rejected caller never reaches the fake; the eligible caller records exactly one tenant. No Nest testing package is used.
Reproduce production policy deliberately, not accidentally
Creating a test application does not automatically prove that the production bootstrapโs global pipes, filters, prefix and adapter options were applied. Share a reviewed configuration function or explicitly install equivalent policies in the test. Keep container compilation, application initialization and listening separate, and do not open a port when the test only needs construction. Report exact runtime and executed boundaries. This course runs the plain spy test only; it does not compile Test modules or exercise a guard, HTTP adapter, test runner integration or source connection. Those remain separately authorized checks in a real project.
Common errors and fixes
A test unexpectedly connects to a real source
- Why it happens
- A provider factory ran before the intended override took effect.
- How to fix it
- Override the exact token before compile and review initialization effects.
- Prevent it next time
- Use explicit synthetic dependency graphs.
A direct controller test misses a broken global pipe
- Why it happens
- The request pipeline and bootstrap configuration were bypassed.
- How to fix it
- Add focused composed tests with the intended configuration.
- Prevent it next time
- Label each test by the boundary it covers.
A denied result follows a protected effect
- Why it happens
- Assertions checked output but not source calls.
- How to fix it
- Assert no protected call on rejection.
- Prevent it next time
- Keep paired eligible/ineligible fixtures.
Check your understanding
1. Why can compiling a module have effects even without a route call?
Show worked solution
The container can instantiate providers and run asynchronous factories that obtain resources. A test must replace or control those dependencies before compilation rather than assuming unused methods mean unused connections. Keep the graph synthetic and verify cleanup for any intentionally created resource in an authorized integration.
2. What should a report say when only the spy function ran?
Show worked solution
Name the local function, runtime and assertions, then state that Nest container and request behavior were not exercised. That is useful narrow evidence. Do not describe it as an end-to-end API check or a token override test when neither boundary participated.
Key takeaways
- A useful test proves identity and effects, not just status.
- Overrides and bootstrap policy must be part of the tested graph.