Code Quality Help

Testing

Test Runner and Framework

All unit tests are executed with Karma as the test runner, using Jasmine as the testing framework. Karma provides a fast, real‑browser environment suitable for Angular libraries and integrates seamlessly with the Angular CLI workspace.

Configuration is shared through a root karma.conf.js, extended by each library as needed. The minimum setup includes:

  • Browser: ChromeHeadless for CI, locally configurable.

  • Reporters: progress and junit (for CI reporting).

  • Frameworks: jasmine.

  • Files: compiled TypeScript spec files colocated with sources (see 5.5).

The Angular CLI’s ng test command is the primary way to invoke tests locally. Customization (e.g., adding polyfills or adjusting timeouts) is allowed at the library level only when strictly necessary and must be documented.

Test Data Generation with @artstesh/forger

To ensure deterministic, maintainable, and realistic test data, all mocks and stubs must be created using the * @artstesh/forger* library. It provides a declarative API for fabricating objects, arrays, and primitive values with controllable variation, making tests more robust and less dependent on fragile hand‑written fixtures.

Usage rules:

  • Every test that requires data structures (models, DTOs, configuration objects) must use forger to generate them.

  • Avoid hard‑coded test data that inadvertently reflects real production values or breaks when models change.

  • Example:

    import { Forger } from '@artstesh/forger'; import { UserModel } from './user.model'; export const user = Forger.create<UserModel>();

The library’s public API must be fully typed; using Forger guarantees that generated objects satisfy those types at the type level.

Event‑Driven Testing with @artstesh/postboy-testing

All libraries and applications rely on an Event‑Driven Architecture powered by @artstesh/postboy. Consequently, any unit test that involves event dispatching, subscription, or handling must use the companion testing library * * @artstesh/postboy-testing**.

This library provides:

  • A controlled PostboyServiceMock (a mock of the event bus) that captures all published events and allows spying on subscriptions.

  • Utilities to simulate event sequences and verify that services react correctly.

Requirements:

  • Services or facades that depend on postboy must be tested with PostboyServiceMock injected.

  • Every event‑producing method should have a test that verifies the event type, payload, and publication under various conditions.

  • Event handlers must be tested by publishing events on the test bus and asserting side effects (state changes, further dispatches, external calls).

  • Do not instantiate the real PostboyService class in unit tests; always use PostboyServiceMock from @artstesh/postboy-testing.

Example:

import {PostboyWorld} from '@artstesh/postboy-testing'; import {FilterService} from './filter.service'; import {FilterResetEvent} from './events'; describe('FilterService', () => { let postboy: PostboyWorld; let service: FilterService; beforeEach(() => { postboy = new PostboyWorld(); service = new FilterService(PostboyWorld.postboy); }); it('should fire FilterResetEvent when reset is called', async () => { service.reset(); // await world.waiter.waitFor(FilterResetEvent, {includeHistory: true}); }); });

Unit Test Requirements and Coverage

Definition of Done for Non‑Visual Classes

Full unit test coverage is mandatory for all services, mappers, utilities, pipes, guards, interceptors, and any other classes that do not directly render HTML. This includes:

  • All public methods, including overloads.

  • All logical branches (if/else, switch cases, early returns).

  • Error handling paths (exceptions thrown, fallbacks).

  • Event‑driven interactions (publishing and reacting to events).

  • Proper test isolation: each test must set up its own context using mocks (from Forger) and the test event bus.

Coverage metrics for these classes are enforced in CI. The target is:

  • Line coverage: ≥ 90%

  • Function coverage: 100% for public API

These thresholds apply to each non‑visual file individually and are checked at the library level.

Unit tests for Angular components (.component.ts files) are recommended but not required for DoD. When written, they should follow the same conventions:

  • Use ng-mocks and mock all dependencies (services, postboy, forger for data).

  • Focus on component logic (input/output interaction, event handling, lifecycle effects), not on the rendered DOM.

  • DOM‑focused tests and visual snapshots are outside the scope of unit testing; they are handled by Storybook and dedicated visual regression tools if needed.

While component tests are not part of the merge gate, teams are encouraged to add them for complex behavioural components (e.g., state machines, dynamic form generators). A low‑effort approach is to test only the component class instance (isolated unit tests without TestBed), which is fully acceptable.

Test Colocation and Naming

Unit test files are colocated with the source file they test. Naming convention:

  • my-service.service.spec.ts for a service named MyService.

  • filter.component.spec.ts for a component class.

  • status.pipe.spec.ts for a pipe.

CI Integration and Coverage Reporting

The CI pipeline executes unit tests for all libraries. The job:

ng test --code-coverage --browsers=ChromeHeadless --watch=false

After the run, coverage reports are generated in coverage/ and analysed:

  • Thresholds are enforced via karma-coverage-istanbul-reporter or similar plugin.

  • If any non‑visual source file fails to meet the threshold, the pipeline fails, blocking the merge.

  • The coverage report is published as an artifact, allowing developers to inspect uncovered branches.

Additionally, test results in JUnit format are collected for trend visualisation in CI dashboards. All tests must pass, with zero failures and zero skipped tests, for the pipeline to succeed.

03 July 2026