Angular Testing is supported by TestBed — a test module that creates an Angular environment in Jasmine/Jest. Knowing when to use a full TestBed vs a lightweight unit test, how to test components with the DOM, and how to mock HTTP calls is the difference between a fast, maintainable test suite and a slow, brittle one.

Key Points

  • TestBed.configureTestingModule(): declares components, imports modules, provides mock services — creates a mini-Angular app
  • ComponentFixture<T>: wrapper around a component instance — fixture.componentInstance, fixture.nativeElement, fixture.debugElement
  • fixture.detectChanges(): triggers Angular's change detection — call after setting @Input or changing signals
  • By.css('.my-class'): DebugElement query — returns Angular-aware element wrappers with component instances
  • HttpClientTestingModule + HttpTestingController: intercept HTTP calls in tests — no real network, flush() to return mock data
  • Spectator: wrapper library that reduces TestBed boilerplate — createComponent(), byLabel(), detectChanges()
  • Jest (via jest-preset-angular): faster than Karma, better snapshot testing, modern test runner
  • Playwright / Cypress: E2E testing — test the full app in a real browser; component testing now supported in both
  • inject() in TestBed: TestBed.inject(Service) to get instances of services from the test injector

Angular testing: TestBed with SpyObj, fakeAsync + tick for debounce, HttpClientTestingModule, HttpTestingController

// Component unit test
describe('SearchComponent', () => {
  let component: SearchComponent;
  let fixture: ComponentFixture<SearchComponent>;
  let searchService: jasmine.SpyObj<SearchService>;

  beforeEach(async () => {
    searchService = jasmine.createSpyObj('SearchService', ['find']);
    searchService.find.and.returnValue(of([{ id: 1, name: 'Widget' }]));

    await TestBed.configureTestingModule({
      imports: [SearchComponent],       // standalone component
      providers: [{ provide: SearchService, useValue: searchService }]
    }).compileComponents();

    fixture = TestBed.createComponent(SearchComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('displays results after typing', fakeAsync(() => {
    const input = fixture.debugElement.query(By.css('input'));
    input.nativeElement.value = 'widget';
    input.nativeElement.dispatchEvent(new Event('input'));
    tick(300);              // advance debounceTime
    fixture.detectChanges();
    const items = fixture.debugElement.queryAll(By.css('.result-item'));
    expect(items.length).toBe(1);
    expect(items[0].nativeElement.textContent).toContain('Widget');
  }));
});

// HTTP testing
describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserService]
    });
    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

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

  it('fetches users', () => {
    service.getUsers().subscribe(users => expect(users).toHaveSize(2));
    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
  });
});

Real-World Example

fakeAsync() + tick(ms) is how you test debounce, delay, and timer-based code without actually waiting. It controls Angular's fake async zone clock. Without it, you'd need done() callbacks and real setTimeout delays. Most Angular testing slowness comes from loading full @SpringBootTest-equivalent TestBed setups for every test — use spyObj mocks aggressively and reserve full TestBed for integration scenarios.