Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
In controller e2e tests, mock only the service and assert the full status contract (400s + failure code), not just 200
Override the service provider with a mock, run the real ValidationPipe/guards, and assert every validation boundary (400) plus the propagated failure status (e.g. 503) alongside the happy path.
Bad example
| 1 | it('returns dead instruments', async () => { |
| 2 | service.getDeadInstruments.mockResolvedValue({ insrefs: [1], total: 1, offset: 0, limit: 30 }); |
| 3 | await request(app.getHttpServer()) |
| 4 | .get('/api/dead-instruments') |
| 5 | .query({ date: '2024-01-01' }) |
| 6 | .expect(200); |
| 7 | }); |
| 8 | // ...and that is the only test. |
Explanation (EN)
Only the happy path is covered. The DTO validation rules (required date, IsInt, Min) and the service-failure mapping to 503 are never exercised, so a regression in the validation decorators or error filter ships silently.
Objašnjenje (HR)
Pokriven je samo sretni put. Validacijska pravila DTO-a (obavezni date, IsInt, Min) i mapiranje neuspjeha servisa u 503 nikad se ne provjeravaju, pa regresija u validacijskim dekoratorima ili error filteru prode neopazeno.
Good example
| 1 | const moduleFixture = await Test.createTestingModule({ imports: [AppModule] }) |
| 2 | .overrideProvider(DeadInstrumentsService) |
| 3 | .useValue({ getDeadInstruments: jest.fn(), getMissingInsrefs: jest.fn() }) |
| 4 | .compile(); |
| 5 | app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); |
| 6 |
|
| 7 | it('returns 400 when date is missing', () => |
| 8 | request(app.getHttpServer()).get('/api/dead-instruments').expect(400)); |
| 9 | it('returns 400 when offset is negative', () => |
| 10 | request(app.getHttpServer()).get('/api/dead-instruments').query({ date: '2024-01-01', offset: -1 }).expect(400)); |
| 11 | it('returns 400 when insrefs exceeds the max', () => |
| 12 | request(app.getHttpServer()).post('/api/dead-instruments/missing').send({ insrefs: Array.from({ length: 1001 }, (_, i) => i) }).expect(400)); |
| 13 | it('returns 503 when the service is unavailable', () => { |
| 14 | service.getDeadInstruments.mockRejectedValue(new ServiceUnavailableException('down')); |
| 15 | return request(app.getHttpServer()).get('/api/dead-instruments').query({ date: '2024-01-01' }).expect(503); |
| 16 | }); |
Explanation (EN)
Mocking only the service keeps the test fast while the real ValidationPipe and exception layer run. Each DTO constraint gets a targeted 400 case and the domain exception is asserted to map to 503 - this is the part unit tests on the service cannot cover.
Objašnjenje (HR)
Mockanje samo servisa drzi test brzim dok se izvrsava pravi ValidationPipe i sloj iznimaka. Svako DTO ogranicenje dobiva ciljani 400 slucaj, a domenska iznimka provjerava se da se mapira u 503 - to je dio koji unit testovi servisa ne mogu pokriti.
Notes (EN)
Mirror the production pipe config (whitelist/transform) in the test app or the 400 assertions will not reflect reality.
Bilješke (HR)
Zrcali konfiguraciju pipe-a iz produkcije (whitelist/transform) u test aplikaciji ili 400 tvrdnje nece odrazavati stvarnost.