| name | ng-mocks-testing |
| description | Write Angular unit tests with ng-mocks (MockBuilder / MockRender / ngMocks) instead of raw TestBed. Use whenever creating or editing any `*.spec.ts` file in this Nx workspace — components, services, pipes, directives, guards, resolvers, interceptors, NgRx effects, or any Angular construct. Triggers on: "write a test for", "add a spec", "unit test this", "cover X with tests", "fix this failing test", editing any existing spec file, or any mention of Jest, Jasmine, TestBed, MockBuilder, MockRender, ngMocks. This skill is the default for authoring tests in this project — use it even when the user doesn't explicitly say "ng-mocks", because the house style requires ng-mocks over hand-rolled TestBed setup. |
Writing Angular unit tests with ng-mocks
This project uses Jest + ng-mocks for every *.spec.ts. ng-mocks replaces the boilerplate of TestBed.configureTestingModule({ declarations, providers, imports }) with a terser, stricter API that auto-mocks every dependency of the thing under test.
Your job when writing or editing a spec: build the test around MockBuilder + MockRender + ngMocks.*. Reach for raw TestBed.configureTestingModule only when something truly cannot be expressed through ng-mocks (very rare — TestBed.inject inside tests is fine and encouraged).
If you're about to hand-write a providers: [] array with a pile of { provide: X, useValue: stub } entries, stop and use MockBuilder(...).mock(X, stub) instead.
Why ng-mocks
MockBuilder(TargetThing, ItsModuleOrImports) creates a test module where every dependency of TargetThing is auto-mocked while the target itself stays real. Nothing is forgotten; nothing leaks real implementations.
- Mock components keep the original selector,
@Input()/@Output(), and @ContentChild — so parent templates still bind correctly and assertions stay type-safe.
ngMocks.faster() shares the compiled TestBed across all its in a describe, collapsing test suite runtime dramatically.
MockRender handles ChangeDetectionStrategy.OnPush, correctly proxies inputs/outputs, and returns a typed fixture.point.componentInstance.
When in doubt, trust ng-mocks to do the right thing and write less setup, not more.
The default skeleton (component)
import {
MockBuilder,
MockRender,
MockedComponentFixture,
ngMocks,
} from 'ng-mocks';
import { TargetComponent } from './target.component';
describe(TargetComponent.name, () => {
let fixture: MockedComponentFixture<TargetComponent>;
let component: TargetComponent;
beforeEach(async () => {
await MockBuilder(TargetComponent)
.mock(SomeService, { doThing: jest.fn().mockReturnValue(of(null)) })
.mock(Store, {
select: jest.fn().mockReturnValue(of(state)),
dispatch: jest.fn(),
});
fixture = MockRender(TargetComponent);
component = fixture.point.componentInstance;
});
it('should render the expected text', () => {
expect(fixture.nativeElement.textContent).toContain('Hello');
});
});
Notes that matter:
MockBuilder(TargetComponent) — for standalone components, passing only the target is enough; ng-mocks discovers the component's imports and mocks them. If dependencies come from a separate module, pass it as the second argument: MockBuilder(TargetComponent, ItsModule).
await MockBuilder(...) — MockBuilder returns a thenable that finalizes the TestBed. Always await (or return) it.
fixture.point.componentInstance is the real TargetComponent. fixture.componentInstance is a proxy for inputs/outputs — use it only when setting inputs via MockRender(TargetComponent, { inputName: value }).
fixture.point.injector.get(SomeService) retrieves the mocked instance when you need to assert on it.
Mocking dependencies
Services
Prefer .mock(ServiceClass, shape) — ng-mocks produces a fully stubbed double whose methods are jest.fn()s, and shape overrides any method you need to control.
await MockBuilder(TargetComponent).mock(OffersService, {
getOffers: jest.fn().mockReturnValue(of([])),
});
To assert on it later:
const offersService = fixture.point.injector.get(
OffersService,
) as jest.Mocked<OffersService>;
expect(offersService.getOffers).toHaveBeenCalledWith('...');
InjectionTokens
MockBuilder.mock is typed for classes. For tokens, cast to never (common in this codebase) or use .provide({ provide: TOKEN, useValue: ... }):
.mock(ANALYTICS as never, { trackEvent: jest.fn() })
.provide({ provide: ANALYTICS, useValue: { trackEvent: jest.fn() } })
NgRx Store
Two idiomatic patterns in this codebase:
-
Lightweight — mock the Store directly with a select shim that returns the right observable per selector:
.mock(Store, {
select: jest.fn().mockImplementation(selector => {
if (selector === selectScreenType) return screenType$.asObservable();
return of(null);
}),
selectSignal: jest.fn((selector: unknown) =>
selector === selectMe ? signal(value) : signal(null),
),
dispatch: jest.fn(),
})
-
provideMockStore — when tests need overrideSelector / refreshState:
.provide(provideMockStore({ selectors: [{ selector: selectMe, value: initial }] }))
Prefer option 1 unless you actually need overrideSelector. It's less magical and keeps selectors explicit.
Child components, directives, pipes
Don't mock them by hand. MockBuilder(TargetComponent) already replaces every child declaration with a mock that preserves the selector, inputs, and outputs. Reach for the mocks via ngMocks.find(ChildComponent) to assert on their bindings or fire their outputs.
Querying the rendered template
Use ngMocks.*, not raw By.css / By.directive, because they're typed and fail fast with readable errors.
| Need | Use |
|---|
| First element matching a CSS selector | ngMocks.find('app-button') |
| First element for a component class | ngMocks.find(ChildComponent) |
| All matching elements | ngMocks.findAll('button') |
| Typed component instance (no debug wrapper) | ngMocks.findInstance(ChildComponent) |
Click with real DOM semantics (respects disabled) | ngMocks.click(button) |
| Dispatch a custom event | ngMocks.trigger(el, 'focus') |
| Set/read a form value | ngMocks.change(input, 'x') / ngMocks.touch(input) |
Read an @Input value on a mock child | ngMocks.input(childEl, 'someInput') |
Calling fixture.detectChanges() after mutating component state or inputs is still your responsibility.
Performance: ngMocks.faster() + beforeAll
Compiling the TestBed in every beforeEach is the biggest cost in Angular test suites. Add ngMocks.faster() at the top of the describe, move the MockBuilder into beforeAll, and reuse the compiled module across tests:
describe(TargetService.name, () => {
ngMocks.faster();
beforeAll(() => MockBuilder(TargetService).mock(HttpClient));
it('...', () => {
const svc = TestBed.inject(TargetService);
});
});
Rules of thumb:
- Use
ngMocks.faster() for services, effects, pipes, and any component suite where the module doesn't change between tests.
- If you create spies in
beforeEach, keep them on a stable object reference so ngMocks.faster() can detect that "the same" spy is being reused. If you need per-test fresh spies, prefer MockInstance(SomeService, 'method', jest.fn()) inside beforeEach.
Pipe tests
Render the pipe via MockRender with a template string; it's tighter than instantiating the pipe class:
describe(FeedbackScoreForCategoryPipe.name, () => {
beforeEach(() =>
MockBuilder(FeedbackScoreForCategoryPipe).provide([
DecimalPipe,
{ provide: LOCALE_ID, useValue: 'en-US' },
]),
);
it('should render formatted score', () => {
const fixture = MockRender('{{ value | feedbackScoreForCategory:stats }}', {
value,
stats,
});
expect(fixture.nativeElement.textContent).toEqual('94.1%');
});
});
Service tests
describe(AbandonedCheckoutService.name, () => {
ngMocks.faster();
beforeAll(() =>
MockBuilder(AbandonedCheckoutService).mock(HttpClient, {
post: jest.fn().mockReturnValue(of({})),
}),
);
it('should POST to the right endpoint', () => {
const svc = TestBed.inject(AbandonedCheckoutService);
svc.report({ orderId: '1' });
expect(TestBed.inject(HttpClient).post).toHaveBeenCalledWith(
expect.stringContaining('/abandoned'),
expect.any(Object),
);
});
});
NgRx Effects tests
This is the project's canonical recipe — follow it closely. See references/effects.md for three worked examples (happy path, error path, cancellation / marble tests).
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { provideMockStore } from '@ngrx/store/testing';
import { MockBuilder, ngMocks } from 'ng-mocks';
import { Observable, of, throwError } from 'rxjs';
import { TestScheduler } from 'rxjs/internal/testing/TestScheduler';
describe(OfferStoreEffects.name, () => {
ngMocks.faster();
const testScheduler = new TestScheduler((actual, expected) =>
expect(actual).toEqual(expected),
);
let actions: Observable<Action>;
beforeAll(() =>
MockBuilder(OfferStoreEffects)
.provide(provideMockStore({}))
.provide(provideMockActions(() => actions))
.mock(OffersService),
);
it('should dispatch success action on load', () => {
testScheduler.run(({ hot, expectObservable }) => {
getOffersSpy().mockReturnValue(of(list));
actions = hot('-a-', { a: loadOffers() });
const effects = TestBed.inject(OfferStoreEffects);
expectObservable(effects.load$).toBe('-r-', { r: setOffers({ list }) });
});
});
function getOffersSpy() {
return TestBed.inject(OffersService).getOffers as jest.Mock;
}
});
Key points:
ngMocks.faster() + beforeAll — effects tests otherwise compile the module on every it.
let actions is reassigned per test; provideMockActions(() => actions) reads the latest value each time.
- Use
TestScheduler / marble syntax (cold, hot) for any effect that involves switchMap/exhaustMap/timing. For plain "subscribe and read" assertions, actions = of(action) is enough.
Anti-patterns — do not do these
- ❌ Hand-building
TestBed.configureTestingModule({ declarations, providers }) with a wall of { provide: X, useValue: {…} }. Use MockBuilder(...).mock(X, shape).
- ❌ Manually declaring
MockComponent(Child) in a declarations array when MockBuilder already auto-mocks every child.
- ❌ Creating a
fixture variable typed as ComponentFixture<T> from MockRender. The correct type is MockedComponentFixture<T>.
- ❌ Asserting through
fixture.debugElement.query(By.css(...)) when ngMocks.find(selectorOrClass) gives you a typed, failing-fast alternative.
- ❌ Putting
MockBuilder in beforeAll without ngMocks.faster() — it'll silently not be shared.
- ❌ Subscribing in tests without
.unsubscribe() for non-completing observables; prefer marbles or firstValueFrom where it fits.
Where to look for more
Reference files in this skill:
references/effects.md — full Effects testing recipes (from the team wiki).
references/cheatsheet.md — one-page API reference for MockBuilder, MockRender, MockProvider, MockInstance, and the most-used ngMocks.* helpers.
references/patterns.md — recipes for common situations: child components, signals-based Store, forms, routing, guards/resolvers, HTTP interceptors.
Test-naming conventions (it('should … when …'), describe block rules, AAA) live in the readable-tests skill. This skill is about what framework APIs to use; follow the naming rules separately.