Skip to content
Skills Library
Programming · Testing

angular-testing

/angular-testing

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Tes

Repository
claude-sdlc ↗
Licence
MIT
Last repository change
26.10.2026

Assessment and security

100/ 100Class: good
Format25 / 25
Frontmatter with name and description, valid name, description up to 1,024 characters, name matching the folder.
Discoverability25 / 25
Description explains when to use the skill, has a reasonable length and uses third person.
Content35 / 35
At least 150 words and at most 500 lines, headings, step lists, examples or code.
Hygiene15 / 15
No placeholders such as TODO, no Windows paths, with a licence.
Riskno signals detected in the scan
Files in this package
1
Scripts
0
HTML comments
0
URLs in SKILL.md
2
Deprecation

No signals detected. Repository changed on 26.10.2026, without references to retired models or APIs.

Licence

MIT, open: use and modify while retaining author attribution.

What it does and when to use it

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Tes

Original author description

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Tes

How it works

  1. Test framework selection
  2. Karma + Jasmine setup (default)
  3. Jest setup (modern)
  4. TestBed basics
  5. Mocking dependencies
  6. HttpClient testing
  7. Component harnesses (@angular/cdk/testing)
  8. Angular Testing Library (RTL-style)
  9. NgRx Effects testing
  10. NgRx Store testing
Original SKILL.md headings · 448 lines, 1,418 words
SKILL.md preview
---
name: angular-testing
description: |
  Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e.

  Use this skill to:
  - Detect runner (Karma+Jasmine vs Jest) and configure correctly.
  - Write component tests with TestBed.
  - Use component harnesses for Material / custom UI components.
  - Mock HttpClient via provideHttpClientTesting + HttpTestingController.
  - Test signal-based inputs with componentRef.setInput().
  - Test NgRx Effects with provideMockActions.

  Do NOT use this skill for:
  - General Angular conventions (see angular-conventions).
  - Routing patterns broadly (see angular-routing — covers testing routes briefly).
  - Form patterns broadly (see angular-forms).
user-invocable: false
paths: ["**/*.spec.ts", "src/**/*.ts"]
---

# Angular Testing

## Test framework selection

| Layer | Framework |
|---|---|
| Component, service, pipe, directive unit | **Karma + Jasmine** (Angular CLI default historically) OR **Jest** (`jest-preset-angular`, modern preferred) |
| Component (alt RTL-style) | `@testing-library/angular` |
| End-to-end | **Playwright** or **Cypress** (`ng add @cypress/schematic`) |

Match what's installed. Modern Angular projects (17+) often switch to Jest for speed; legacy projects stick with Karma.

## Karma + Jasmine setup (default)

`ng new` ships with Karma + Jasmine pre-configured. Tests in `*.spec.ts`. Run via `ng test` (watch mode) or `ng test --watch=false --browsers=ChromeHeadless` (CI).

`karma.conf.js` and `tsconfig.spec.json` already in project. Don't modify unless customizing.

## Jest setup (modern)

```bash
pnpm add -D jest jest-preset-angular @types/jest
```

`jest.config.ts`:

```ts
import type { Config } from 'jest';

const config: Config = {
  preset: 'jest-preset-angular',
  setupFilesAfterEach: ['<rootDir>/setup-jest.ts'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  testPathIgnorePatterns: ['/node_modules/', '/dist/'],
};

export default config;
```

`setup-jest.ts`:

```ts
import 'jest-preset-angular/setup-jest';
```

Update `tsconfig.spec.json`:

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "types": ["jest", "node"]
  }
}
```

In `package.json`:

```json
"scripts": {
  "test": "jest"
}
```

Remove Karma config / packages from `devDependencies` (cleanup), or keep both temporarily during migration.

## TestBed basics

```ts
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { UserCardComponent } from './user-card.component';

describe('UserCardComponent', () => {
  let fixture: ComponentFixture<UserCardComponent>;
  let component: UserCardComponent;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [UserCardComponent],         // standalone — import the component itself
    }).compileComponents();

    fixture = TestBed.createComponent(UserCardComponent);
    component = fixture.componentInstance;
  });

  it('renders user name', () => {
    component.user = { id: '1', name: 'Alice', email: 'a@b.c' };
    fixture.detectChanges();
    expect(fixture.nativeElement.textContent).toContain('Alice');
  });
});
```

For NgModule projects: `TestBed.configureTestingModule({ declarations: [UserCardComponent], imports: [...required modules...] })`.

`fixture.detectChanges()` triggers Angular's change detection — re-render with current state. Required after any input/state change.

### Signal-based inputs (Angular 17.1+)

```ts
beforeEach(async () => {
  await TestBed.configureTestingModule({ imports: [UserCardComponent] }).compileComponents();
  fixture = TestBed.createComponent(UserCardComponent);
});

it('renders user name from signal input', () => {
  fixture.componentRef.setInput('user', { id: '1', name: 'Alice', email: 'a@b.c' });
  fixture.detectChanges();
  expect(fixture.nativeElement.textContent).toContain('Alice');
});
```

`componentRef.setInput('name', value)` is the correct way to set signal-based inputs. Direct `component.user = ...` won't trigger reactivity.

## Mocking dependencies

```ts
const mockUsersService = {
  loadUsers: jest.fn().mockResolvedValue(undefined),
  users: signal([{ id: '1', name: 'Alice' }]),
  loading: signal(false),
};

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [UsersListComponent],
    providers: [
      { provide: UsersService, useValue: mockUsersService },
    ],
  }).compileComponents();
});
```

`useValue` swaps the real service with a mock. `useFactory: () => mock` for factory-style.

For Karma+Jasmine: replace `jest.fn()` with `jasmine.createSpy('name')` or `jasmine.createSpyObj('UsersService', ['loadUsers'])`.

## HttpClient testing

```ts
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { UsersService } from './users.service';

describe('UsersService', () => {
  let service: UsersService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
      ],
    });

    service = TestBed.inject(UsersService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();                       // assert no outstanding requests
  });

  it('fetches users', () => {
    let result: User[] | undefined;
    service.loadUsers().subscribe((users) => (result = users));

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush([{ id: '1', name: 'Alice', email: 'a@b.c' }]);

    expect(result).toEqual([{ id: '1', name: 'Alice', email: 'a@b.c' }]);
  });

  it('handles error', () => {
    let error: any;
    service.loadUsers().subscribe({
      error: (err) => (error = err),
    });

    const req = httpMock.expectOne('/api/users');
    req.error(new ProgressEvent('Network error'), { status: 500 });

    expect(error).toBeDefined();
  });
});
```

`httpMock.verify()` in `afterEach` fails the test if any HTTP requests were made but not handled — catches accidental real network calls.

## Component harnesses (`@angular/cdk/testing`)

For Material UI (or custom components with harnesses):

```ts
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatInputHarness } from '@angular/material/input/testing';

describe('LoginComponent', () => {
  let fixture: ComponentFixture<LoginComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    await TestBed.configureTestingModule({ imports: [LoginComponent] }).compileComponents();
    fixture = TestBed.createComponent(LoginComponent);
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('logs in on submit', async () => {
    const emailInput = await loader.getHarness(MatInputHarness.with({ selector: '[formControlName="email"]' }));
    await emailInput.setValue('a@b.c');

    const submitBtn = await loader.getHarness(MatButtonHarness.with({ text: /Log in/i }));
    await submitBtn.click();

    fixture.detectChanges();
    expect(/* assert post-submit state */);
  });
});
```

Harnesses provide stable APIs that survive Material upgrades — preferred over `By.css('.mat-button')` queries.

For custom components, write your own harness extending `ComponentHarness`. See Material docs.

## Angular Testing Library (RTL-style)

```bash
pnpm add -D @testing-library/angular @testing-library/jest-dom
```

```ts
import { render, screen } from '@testing-library/angular';
import { UserCardComponent } from './user-card.component';

it('renders user name', async () => {
  await render(UserCardComponent, {
    inputs: { user: { id: '1', name: 'Alice', email: 'a@b.c' } },
  });

  expect(screen.getByText('Alice')).toBeInTheDocument();
});

it('emits delete on click', async () => {
  const deleteSpy = jest.fn();
  await render(UserCardComponent, {
    inputs: { user: { id: '1', name: 'Alice', email: 'a@b.c' } },
    on: { delete: deleteSpy },
  });

  const button = screen.getByRole('button', { name: /delete/i });
  await userEvent.click(button);

  expect(deleteSpy).toHaveBeenCalledWith('1');
});
```

Same query priority as React/Vue Testing Library: `getByRole` > `getByLabelText` > `getByText` > `getByTestId`.

For new tests in modern Angular projects, RTL-style is often cleaner than raw TestBed + DOM queries.

## NgRx Effects testing

```bash
pnpm add -D @ngrx/effects @ngrx/store
```

```ts
import { Actions } from '@ngrx/effects';
import { provideMockActions } from '@ngrx/effects/testing';
import { TestBed } from '@angular/core/testing';
import { Observable, of, throwError } from 'rxjs';
import { UsersEffects } from './users.effects';
import { UsersActions } from './users.actions';

describe('UsersEffects', () => {
  let actions$: Observable<any>;
  let effects: UsersEffects;
  let mockHttp: { get: jest.Mock };

  beforeEach(() => {
    mockHttp = { get: jest.fn() };

    TestBed.configureTestingModule({
      providers: [
        UsersEffects,
        provideMockActions(() => actions$),
        { provide: HttpClient, useValue: mockHttp },
      ],
    });

    effects = TestBed.inject(UsersEffects);
  });

  it('loadUsers$ → loadUsersSuccess on HTTP success', (done) => {
    const users = [{ id: '1', name: 'Alice' }];
    mockHttp.get.mockReturnValue(of(users));

    actions$ = of(UsersActions.loadUsers());

    effects.loadUsers$.subscribe((action) => {
      expect(action).toEqual(UsersActions.loadUsersSuccess({ users }));
      done();
    });
  });

  it('loadUsers$ → loadUsersFailure on HTTP error', (done) => {
    mockHttp.get.mockReturnValue(throwError(() => new Error('Network down')));

    actions$ = of(UsersActions.loadUsers());

    effects.loadUsers$.subscribe((action) => {
      expect(action).toEqual(UsersActions.loadUsersFailure({ error: 'Network down' }));
      done();
    });
  });
});
```

`provideMockActions(() => actions$)` lets you control the input action stream. Effects fire when `actions$` emits a matching action.

## NgRx Store testing

```ts
import { provideMockStore, MockStore } from '@ngrx/store/testing';

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [UsersListComponent],
    providers: [
      provideMockStore({
        initialState: { users: { items: [{ id: '1', name: 'Alice' }], loading: false, error: null } },
        selectors: [
          { selector: selectUserCount, value: 1 },
        ],
      }),
    ],
  });

  store = TestBed.inject(MockStore);
});

it('renders user list from store', () => {
  fixture.detectChanges();
  expect(fixture.nativeElement.textContent).toContain('Alice');
});

it('overrides selector value', () => {
  store.overrideSelector(selectUserCount, 5);
  store.refreshState();
  expect(/* assert based on count = 5 */);
});
```

## Cypress / Playwright e2e

### Cypress (`ng add @cypress/schematic`)

```ts
// cypress/e2e/login.cy.ts
describe('login', () => {
  it('user can log in', () => {
    cy.visit('/login');
    cy.get('[formControlName=email]').type('a@b.c');
    cy.get('[formControlName=password]').type('longenough');
    cy.contains('button', /log in/i).click();
    cy.url().should('include', '/dashboard');
    cy.contains('Welcome').should('be.visible');
  });
});
```

### Playwright

```ts
// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  webServer: {
    command: 'ng serve',
    url: 'http://localhost:4200',
    reuseExistingServer: !process.env.CI,
  },
  use: { baseURL: 'http://localhost:4200', trace: 'on-first-retry' },
});

// e2e/login.spec.ts
import { test, expect } from '@playwright/test';

test('user can log in', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel(/email/i).fill('a@b.c');
  await page.getByLabel(/password/i).fill('longenough');
  await page.getByRole('button', { name: /log in/i }).click();
  await expect(page).toHaveURL(/\/dashboard/);
});
```

## Coverage discipline

Target ≥80% on:
- Services (state holders, HTTP wrappers).
- Pipes / directives with logic.
- Components with conditional rendering or event handling.
- NgRx reducers, selectors, effects.

Skip / lower bar:
- Pure presentational components (snapshot churn outweighs value).
- `app.module.ts` / `*.routes.ts` (configuration).
- `main.ts` (bootstrap).

Karma config:
```js
codeCoverage: { include: ['src/**/*.ts'], exclude: ['src/main.ts', 'src/**/*.module.ts', '**/*.spec.ts'] }
```

Jest config:
```ts
coveragePathIgnorePatterns: ['/node_modules/', '\\.module\\.ts$', 'src/main.ts']
```

## Iteration cap (from QA agent)

The qa-engineer agent has a hard 3-attempt cap on fixing failing tests. After attempt #3, mark `xit(...)` (Jasmine) or `it.skip(...)` (Jest) with a comment, report in QA summary.

## Anti-patterns

- ❌ Forgetting `httpMock.verify()` in `afterEach` — leaves outstanding requests undetected.
- ❌ Setting signal-based inputs via property assignment (`component.user = X`) instead of `componentRef.setInput()`.
- ❌ Forgetting `fixture.detectChanges()` after state changes — assertions see stale DOM.
- ❌ Testing private methods (test public surface — props in, emits out, rendered DOM).
- ❌ `getByTestId` everywhere instead of accessible queries.
- ❌ Real network calls in unit tests (slow, flaky) — use `provideHttpClientTesting`.
- ❌ Snapshot tests of large component trees — review noise.
- ❌ E2E tests against real backend without seed/mocked endpoints.
- ❌ Mocking the SUT instead of its dependencies.
- ❌ Karma `--watch=true` in CI — tests hang waiting for changes.
- ❌ Mixing `jasmine.createSpy()` and `jest.fn()` in the same project — pick one runner.

Versions

RepositoryScoreLicence
This page · aratkruglik/claude-sdlc100MIT
HoangNguyen0403/agent-skills-standard100MIT
qualcomm/qualcomm-ui100BSD-3-Clause
claude-dev-suite/claude-dev-suite95MIT

Database snapshot: 13.11.2026 · package version 6d1b4f150c4a (SHA-256 digest of the entire package)

Downloads in the last 30 days: 0 · ranking

Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.

Similar skills

  • 95angular
    Use when building or refactoring Angular applications — choosing between signals, RxJS, and NgRx for state, configuring routing with guards and lazy loading, optimizing change detection, or writing TestBed component tests.
  • 95karma-skill
    Generates Karma test runner configurations for browser-based JavaScript testing. Works with Jasmine, Mocha, or QUnit. Use when user mentions "Karma", "karma.conf.js", "browser test runner". Triggers on: "Karma", "karma.conf", "karma test runner", "browser-based JS test".
  • 90frontend-testing
    Testing strategy and setup for TypeScript frontends: Vitest with Testing Library for React and Next.js, the Angular Vitest builder (@angular/build:unit-test) with TestBed and component harnesses, MSW for API mocking, Playwright for end-to-end, coverage thresholds and CI wiring.…
  • 80Angular HttpClient Migration v6
    Migrate from Angular HttpModule and Http to HttpClientModule and HttpClient during the Angular 5 to 6 era. Use when legacy HTTP APIs block modern Angular usage.
  • 100react-testing
    React component testing with React Testing Library, Vitest/Jest, MSW for network mocking, accessibility assertions with axe, and the decision boundary between component tests and Playwright/Cypress end-to-end runs. Use when writing or fixing tests for React components, hooks, or pages.
  • 100react-testing
    Testing React SPAs: React Testing Library + Vitest/Jest for components and hooks, msw for network mocks, Playwright/Cypress for e2e. Query priority, user events, async assertions, mocking patterns. Use this skill to: - Pick the right runner (Vitest vs Jest) and setup. - Write…

Feedback and reports

Your message goes to Bartosz Sokoliński. An email address is needed only for a reply. See the privacy policy.