本文へ移動
cccskills
無料GitHub で公開

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(). - 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).

インストール方法を見る

含まれるファイル(1)

  • SKILL.md13.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Angular Testing

Test framework selection

LayerFramework
Component, service, pipe, directive unitKarma + Jasmine (Angular CLI default historically) OR Jest (jest-preset-angular, modern preferred)
Component (alt RTL-style)@testing-library/angular
End-to-endPlaywright 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)

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

jest.config.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:

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

Update tsconfig.spec.json:

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

In package.json:

"scripts": {
  "test": "jest"
}

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

TestBed basics

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+)

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

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

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):

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)

pnpm add -D @testing-library/angular @testing-library/jest-dom
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

pnpm add -D @ngrx/effects @ngrx/store
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

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)

// 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

// 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:

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

Jest config:

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.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

ASP.NET Core web framework conventions: Minimal API vs MVC controllers, Program.cs composition, DI lifetimes (Scoped/Singleton/Transient), Options pattern with IOptions<T>, middleware ordering (HTTPS redirect → routing → authentication → authorization), model binding and validation (FluentValidation / DataAnnotations), ProblemDetails error handling, structured logging with ILogger<T>, configuration layering (appsettings.json + environment variables + User Secrets), and health checks. Works alongside csharp-foundation:csharp-conventions and aspnet-core-plugin:efcore-patterns. Use this skill to: - Compose Program.cs correctly — register services, configure middleware in the right order, map endpoints. - Apply the Options pattern to avoid passing raw IConfiguration into services. - Write Minimal API endpoint groups with typed results and authorization. - Handle cross-cutting errors uniformly with ProblemDetails. - Configure structured logging and health checks for production readiness. Do NOT use this skill for: - EF Core entity configuration and migrations — see aspnet-core-plugin:efcore-patterns. - C# language idioms — see csharp-foundation:csharp-conventions. - Testing — see csharp-foundation:dotnet-testing.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する