mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-14 02:10:07 +08:00
165 lines
4.4 KiB
Markdown
165 lines
4.4 KiB
Markdown
---
|
|
paths:
|
|
- "**/*.spec.ts"
|
|
- "**/*.test.ts"
|
|
---
|
|
# Angular Testing
|
|
|
|
> This file extends [common/testing.md](../common/testing.md) with Angular specific content.
|
|
|
|
## Test Runner
|
|
|
|
Use the test runner configured by the project. Check `angular.json` and `package.json`; Angular projects commonly use Vitest, Jest, or Jasmine + Karma.
|
|
|
|
```bash
|
|
ng test # watch mode
|
|
ng test --no-watch # CI mode
|
|
```
|
|
|
|
## TestBed Setup
|
|
|
|
For standalone components, import the component directly. Call `compileComponents()` for components with external templates.
|
|
|
|
```typescript
|
|
describe('UserCardComponent', () => {
|
|
let fixture: ComponentFixture<UserCardComponent>;
|
|
|
|
beforeEach(async () => {
|
|
await TestBed.configureTestingModule({
|
|
imports: [UserCardComponent],
|
|
}).compileComponents();
|
|
|
|
fixture = TestBed.createComponent(UserCardComponent);
|
|
});
|
|
});
|
|
```
|
|
|
|
## Signal Inputs
|
|
|
|
Set signal-based inputs via `fixture.componentRef.setInput()`:
|
|
|
|
```typescript
|
|
fixture.componentRef.setInput('user', mockUser);
|
|
fixture.detectChanges();
|
|
```
|
|
|
|
## Component Harnesses
|
|
|
|
Prefer Angular CDK component harnesses over direct DOM queries for UI interaction. Harnesses are more resilient to markup changes.
|
|
|
|
```typescript
|
|
import { HarnessLoader } from '@angular/cdk/testing';
|
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
|
import { MatButtonHarness } from '@angular/material/button/testing';
|
|
|
|
let loader: HarnessLoader;
|
|
|
|
beforeEach(() => {
|
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
|
});
|
|
|
|
it('triggers save on button click', async () => {
|
|
const button = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));
|
|
await button.click();
|
|
expect(saveSpy).toHaveBeenCalled();
|
|
});
|
|
```
|
|
|
|
## Router Testing
|
|
|
|
Use `RouterTestingHarness` for components that depend on the router:
|
|
|
|
```typescript
|
|
import { RouterTestingHarness } from '@angular/router/testing';
|
|
|
|
it('renders user on navigation', async () => {
|
|
const harness = await RouterTestingHarness.create();
|
|
const component = await harness.navigateByUrl('/users/1', UserDetailComponent);
|
|
expect(component.userId()).toBe('1');
|
|
});
|
|
```
|
|
|
|
## Async Testing
|
|
|
|
Use `fakeAsync` + `tick` for controlled async. Use `waitForAsync` for real async with `fixture.whenStable()`.
|
|
|
|
```typescript
|
|
it('loads user after delay', fakeAsync(() => {
|
|
const service = TestBed.inject(UserService);
|
|
vi.spyOn(service, 'getUser').mockReturnValue(of(mockUser));
|
|
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.querySelector('.name').textContent).toBe(mockUser.name);
|
|
}));
|
|
```
|
|
|
|
## HTTP Testing
|
|
|
|
```typescript
|
|
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
|
import { HttpTestingController } from '@angular/common/http/testing';
|
|
|
|
beforeEach(() => {
|
|
TestBed.configureTestingModule({
|
|
providers: [provideHttpClient(), provideHttpClientTesting()],
|
|
});
|
|
httpMock = TestBed.inject(HttpTestingController);
|
|
});
|
|
|
|
afterEach(() => httpMock.verify());
|
|
```
|
|
|
|
## Service Testing
|
|
|
|
Inject services directly without a component fixture:
|
|
|
|
```typescript
|
|
describe('UserService', () => {
|
|
let service: UserService;
|
|
|
|
beforeEach(() => {
|
|
TestBed.configureTestingModule({
|
|
providers: [provideHttpClient(), provideHttpClientTesting()],
|
|
});
|
|
service = TestBed.inject(UserService);
|
|
});
|
|
});
|
|
```
|
|
|
|
## What to Test
|
|
|
|
- **Services**: All public methods, error paths, HTTP interactions
|
|
- **Components**: Input/output bindings, rendered output for key states, user interactions via harnesses
|
|
- **Pipes**: Pure transformation — plain unit tests, no TestBed needed
|
|
- **Guards/Resolvers**: Return values for allowed and denied states using `RouterTestingHarness`
|
|
|
|
## E2E Testing
|
|
|
|
Use the project's configured E2E framework, such as Cypress or Playwright, for critical user flows.
|
|
|
|
```typescript
|
|
describe('Login flow', () => {
|
|
it('redirects to dashboard on valid credentials', () => {
|
|
cy.visit('/login');
|
|
cy.get('[data-cy=email]').type('user@example.com');
|
|
cy.get('[data-cy=password]').type('password123');
|
|
cy.get('[data-cy=submit]').click();
|
|
cy.url().should('include', '/dashboard');
|
|
});
|
|
});
|
|
```
|
|
|
|
- Add `data-cy` attributes to interactive elements for stable selectors
|
|
- Do not rely on CSS classes or text content for selectors in E2E tests
|
|
|
|
## Coverage
|
|
|
|
Target ≥80% for services and pipes. Components: test behaviour, not implementation details.
|
|
|
|
## Skill Reference
|
|
|
|
See skill: `angular-developer` for comprehensive testing patterns, harness usage, and async best practices.
|