Org Skills

angular-testing

Design and write tests for Angular applications — unit tests with Vitest or Jest, component tests with TestBed and Angular Testing Library, HttpTestingController, testing signals, SignalStore/NgRx, pipes, directives, guards, interceptors, and end-to-end tests with Playwright; plus migrating off Karma/Jasmine.

Download .zip Raw Source
When agents use itUse this whenever the user asks how to test an Angular component, service, store, form, route guard, or pipe; wants to set up or speed up an Angular test pipeline; asks about mocking HttpClient, testing OnPush components, flaky tests, or "what should I test". Also apply it when generating tests for Angular code the user just wrote, even if they don't say "test".

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill angular-testing -a github-copilot
# or with the org installer (adds .github/skills/angular-testing):
npx -y github:AGCO-Global/org-skills add skill angular-testing

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill angular-testing
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill angular-testing -g

Installs the frontend-angular-skills plugin, which bundles all Frontend / Angular skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install frontend-angular-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill angular-testing -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill angular-testing -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill angular-testing -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill angular-testing -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

Angular Testing

Test behaviour through the public surface: rendered DOM, emitted outputs, HTTP requests made, navigation triggered. Avoid testing private methods or internal signal values directly unless they are the public API (a store).

1. Runner and setup

  • Vitest via the Angular CLI unit-test builder (@angular/build:unit-test) on current versions; Jest (jest-preset-angular) on older projects. Karma is deprecated — if the user is on it, the first deliverable is a migration plan.
  • Angular Testing Library (@testing-library/angular) for component tests — role-based queries, userEvent, less TestBed ceremony.
  • provideZonelessChangeDetection() in tests if the app is zoneless; otherwise tests behave differently from production.
  • Shared render helper with default providers (router, HttpClient testing, i18n) in src/testing/.
  • provideHttpClient() + provideHttpClientTesting() for HTTP; MSW if you want the same mocks across unit, Storybook, and E2E.

2. Testing pyramid for Angular

Layer Tool Share Target
Static strict, strictTemplates, @angular-eslint (incl. template a11y) free Type & template errors
Unit Vitest/Jest, plain functions ~25% model/ logic, pipes, pure helpers, reducers
Component/integration Testing Library + TestBed + HttpTestingController/MSW ~55% Page + children rendered together, real store, mocked HTTP
Visual/interaction Storybook + @storybook/test, addon-a11y ~10% shared/ui states
E2E Playwright ~10% Critical journeys against a prod build

3. Component tests — patterns

it('shows an error when the save request fails', async () => {
  const user = userEvent.setup();
  await render(OrderEditPage, {
    providers: [provideHttpClient(), provideHttpClientTesting(), provideRouter([])],
    inputs: { id: '42' },          // signal inputs, incl. route-bound ones
  });
  const http = TestBed.inject(HttpTestingController);
  http.expectOne('/api/orders/42').flush(orderFixture);

  await user.click(screen.getByRole('button', { name: /save/i }));
  http.expectOne({ method: 'PUT', url: '/api/orders/42' }).flush(null, { status: 500, statusText: 'Server Error' });

  expect(await screen.findByRole('alert')).toHaveTextContent(/could not save/i);
  http.verify();
});

Rules:

  • Query by role and accessible name; data-testid only with a comment explaining why no role fits.
  • userEvent over fireEvent; awaits settle change detection.
  • OnPush/zoneless: after changing a signal input in a test, use fixture.componentRef.setInput('x', v) or Testing Library's rerender({ inputs }), then await fixture.whenStable(); never call detectChanges() blindly in a loop.
  • Outputs: capture with on: { save: spy } in render (or outputRef.subscribe on the fixture) and assert the emitted payload.
  • Child components: render them for real. Stub only when the child is heavy (chart library) or has side effects — use a stub with the same selector and input()s, or NO_ERRORS_SCHEMA sparingly.
  • @defer blocks: use fixture.getDeferBlocks() and deferBlock.render(DeferBlockState.Complete) to test deferred content; TestBed.configureTestingModule({ deferBlockBehavior: DeferBlockBehavior.Playthrough }) to trigger naturally.
  • Router: provideRouter(routes) + RouterTestingHarness to navigate and assert the activated component; provideLocationMocks().

4. Services, stores, and signals

  • Signal services: TestBed.runInInjectionContext or TestBed.inject(Service); call methods; assert service.items(); TestBed.flushEffects() (or fixture.detectChanges()) to run effects.
  • computed: assert values after changing upstream signals — no await needed; computeds are synchronous.
  • toSignal sources: provide the Observable via a Subject in the mocked dependency, next() values, assert the signal.
  • NgRx SignalStore: instantiate via TestBed.inject(OrdersStore) with mocked API providers; call withMethods methods; for rxMethod use fake HTTP and await the settled state. Assert on state signals and computeds, not internals.
  • NgRx Store: provideMockStore({ initialState }), overrideSelector, test reducers as pure functions and effects with provideMockActions + marbles or firstValueFrom.
  • HTTP services: HttpTestingControllerexpectOne, flush, assert request body/headers, verify() in afterEach.

5. Pipes, directives, guards, interceptors

  • Pipes: pure functions — new MyPipe().transform(input); for pipes with DI use TestBed.runInInjectionContext(() => new Pipe()).
  • Directives: render a small host component using the directive; assert DOM/attribute changes and emitted events.
  • Functional guards: TestBed.runInInjectionContext(() => canActivateAuth(route, state)); assert true/UrlTree.
  • Interceptors: provide via provideHttpClient(withInterceptors([authInterceptor])) and assert on HttpTestingController request headers.

6. Forms

  • Reactive forms: type into inputs with userEvent, submit, assert validation messages via role="alert" or aria-describedby; also assert form.valid where the form is the public API of a component.
  • Test the behaviour of validators (message shown), not Validators.required itself.
  • Async validators: flush HTTP in HttpTestingController, then await screen.findByText(...).

7. Storybook

  • Stories for every shared/ui component state; argTypes mapped to input()s.
  • play functions with @storybook/test for interaction; run in CI with the test-runner.
  • addon-a11y with CI failure on violations for the design system.

8. Playwright E2E

  • Journeys, not pages: login → create → edit → verify.
  • getByRole locators; storageState for auth; test.step.
  • Run against ng build output served statically (or the SSR server) in CI; never ng serve.
  • Seed data via API in beforeEach; clean up after. Retries only in CI; trace on first retry.
  • Tag smoke tests (@smoke) to run on every PR; full suite nightly.

9. Karma → Vitest/Jest migration plan

  1. Add the new runner alongside Karma; convert one feature's specs; get CI green with both.
  2. Replace jasmine.createSpyvi.fn()/jest.fn(), spyOnvi.spyOn, fakeAsync/tick → keep (works) or fake timers, done callbacks → async/await.
  3. Add Angular Testing Library; rewrite the flakiest specs first with role queries.
  4. Remove Karma, jasmine, karma.conf.js; update angular.json test target.
  5. Add coverage thresholds on changed files only.

10. When asked "write tests for this"

  1. Enumerate observable behaviours: initial render, each input variation, each user action, each output, loading/error/empty states, a11y roles.
  2. Reuse the shared render helper and existing fixtures/handlers.
  3. One it per behaviour, sentence-style names.
  4. Run the tests; if you need detectChanges() more than once per interaction, the component likely has hidden imperative state — flag it.

Flaky-test triage

Symptom Cause Fix
Passes alone, fails in suite Root-provided service holding state across tests Reset in afterEach, or provide at component level
expectOne found none / found 2 Request made in effect() or duplicated by AsyncPipe resubscribe flushEffects, shareReplay, check template
Timing failures setTimeout in code, zone vs zoneless mismatch Fake timers; align test CD mode with app
NG0100 in tests only Writing signals in ngAfterViewInit Fix data flow, don't setTimeout