Angular Testing Interview Questions and Answers
20 hand-picked Angular Testing interview questions with
detailed answers. Open the interactive version above to search, filter
by difficulty, run code, bookmark questions and track your progress.
Unit vs integration vs e2e tests (the testing pyramid).
- Unit — one class/function in isolation, dependencies mocked. Fast, many.
- Integration — several units together (a component + its template + a real child).
- E2E — the whole app in a browser, simulating a real user. Slow, few.
The pyramid: lots of unit tests, fewer integration, fewest e2e.
What are Jasmine and Karma in Angular?
Jasmine is the testing framework — describe/it/expect with matchers and spies. Karma is the test runner that launches real browsers and reports results. (Newer Angular is moving to Jest/Vitest + web-test-runner.)
describe('MathUtil', () => {
it('adds', () => { expect(add(2, 3)).toBe(5); });
});
What is TestBed and configureTestingModule?
TestBed creates an Angular testing module — a mini environment where you declare/import the component under test and provide (usually mocked) dependencies. TestBed.createComponent() then gives you a ComponentFixture.
await TestBed.configureTestingModule({
imports: [MyComponent],
providers: [{ provide: DataService, useValue: mockData }]
}).compileComponents();
ComponentFixture — detectChanges, nativeElement, debugElement?
The fixture wraps the component + its DOM. detectChanges() runs change detection (nothing renders until you call it). nativeElement is the raw DOM; debugElement is Angular's wrapper with query(By.css()) for finding elements.
const fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
const h1 = fixture.debugElement.query(By.css('h1')).nativeElement;
expect(h1.textContent).toContain('Hello');
How do spies work (spyOn, createSpy, returnValue)?
A spy records calls and can fake return values. spyOn(obj, 'method') replaces a real method; jasmine.createSpyObj builds a whole mock. Assert with toHaveBeenCalledWith.
const svc = jasmine.createSpyObj('DataService', ['load']);
svc.load.and.returnValue(of([1, 2]));
expect(svc.load).toHaveBeenCalledWith(42);
How do you test HttpClient calls?
Import HttpClientTestingModule and inject HttpTestingController. It intercepts requests so no real network happens: expectOne(url) asserts the request, flush(data) returns a fake response, and verify() ensures nothing is outstanding.
service.getUsers().subscribe(u => expect(u.length).toBe(2));
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush([{ id: 1 }, { id: 2 }]);
fakeAsync + tick vs async + whenStable.
fakeAsync runs a test in a virtual time zone; tick(ms) advances timers synchronously and flush() drains all pending ones — ideal for setTimeout/debounce. async/whenStable waits for real async tasks (promises) to settle.
it('debounces', fakeAsync(() => {
component.onType('a');
tick(300);
expect(component.results.length).toBe(1);
}));
How do you test an observable?
Subscribe and assert inside the callback. For synchronous streams (of) assertions run immediately; for async use fakeAsync/done. Verify emitted values and completion.
service.data$.subscribe(v => {
expect(v).toEqual([1, 2, 3]);
});
What is marble testing?
A way to test RxJS timing using ASCII 'marble diagrams' with the TestScheduler. Characters represent virtual time: - a frame, letters emissions, | complete, # error. Great for operators like debounceTime and switchMap.
testScheduler.run(({ cold, expectObservable }) => {
const source$ = cold('-a-b-c|');
expectObservable(source$.pipe(map(x => x.toUpperCase())))
.toBe('-A-B-C|');
});
How do you unit-test a service?
Services are plain classes — instantiate directly or via TestBed.inject(), mock their dependencies, call methods, and assert results/side effects. No fixture/DOM needed unless the service touches HTTP.
const service = TestBed.inject(CalcService);
expect(service.total([1, 2, 3])).toBe(6);
How do you test a pipe?
Pipes are pure functions — instantiate the class and assert transform() output for various inputs. No TestBed required.
const pipe = new TruncatePipe();
expect(pipe.transform('hello world', 5)).toBe('hello…');
How do you test a directive?
Create a tiny host component whose template uses the directive, render it via TestBed, then assert the directive's effect on the host element (classes, styles, events).
@Component({ template: `<div appHighlight></div>` })
class HostComponent {}
How do you test @Input and @Output?
Input: set the property on component, call detectChanges(), assert the rendered result. Output: subscribe to the EventEmitter (or spy on it), trigger the action, and assert it emitted the expected payload.
let emitted;
component.saved.subscribe(v => (emitted = v));
component.onSave();
expect(emitted).toEqual(expectedItem);
What are CDK Component Harnesses?
From @angular/cdk/testing: a harness is a reusable API for interacting with a component in tests without depending on its DOM structure — so tests don't break when internal markup changes. Angular Material ships harnesses for its components.
const loader = TestbedHarnessEnvironment.loader(fixture);
const button = await loader.getHarness(MatButtonHarness);
await button.click();
How do you test routing/navigation?
Provide the router in test mode (RouterTestingModule or provideRouter([])), spy on Router.navigate, or use a RouterTestingHarness to drive real navigation and assert the activated component.
const router = TestBed.inject(Router);
const spy = spyOn(router, 'navigate');
component.logout();
expect(spy).toHaveBeenCalledWith(['/login']);
What is code coverage and is 100% the goal?
Coverage measures which lines/branches your tests execute (ng test --code-coverage). It shows untested code but not test quality — 100% coverage of trivial getters proves little. Aim for meaningful coverage of logic and edge cases, not a vanity number.
ng test --code-coverage
E2E options: Cypress / Playwright vs Protractor.
Protractor was Angular's original e2e tool — now deprecated. Modern choices are Cypress (great DX, time-travel debugging) and Playwright (multi-browser, fast, auto-wait). They drive the real app and assert user-visible behaviour.
// Cypress
cy.visit('/login');
cy.get('[data-cy=email]').type('a@b.com');
cy.contains('Sign in').click();
What makes a good unit test?
- Tests behaviour, not implementation details.
- Fast, isolated, and deterministic (no real network/time).
- One logical assertion/concept per test; descriptive names.
- Arrange-Act-Assert structure; mock external dependencies.
How do you mock dependencies in Angular tests?
Provide fakes in TestBed via providers: [{ provide: RealService, useValue: mock }] or useClass/useFactory. Build mocks with jasmine.createSpyObj (or Jest fns) and return Observables with of(...)/throwError(...) to match real APIs.
const userService = jasmine.createSpyObj<UserService>('UserService', ['getUser']);
userService.getUser.and.returnValue(of({ id: '1', name: 'Ann' }));
await TestBed.configureTestingModule({
imports: [UserComponent],
providers: [{ provide: UserService, useValue: userService }]
}).compileComponents();
How do you test OnPush components correctly?
OnPush components re-render on input reference changes, events, or explicit mark-for-check — not on random field mutations. In tests, set inputs with fixture.componentRef.setInput() (or new input object references), trigger events, then detectChanges(). Mutating an input object in place often won’t update the view.
fixture.componentRef.setInput('user', { id: '1', name: 'Ann' });
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Ann');
// new reference required for another update
fixture.componentRef.setInput('user', { id: '1', name: 'Bob' });
fixture.detectChanges();