Why Spectator makes Angular testing enjoyable

Angular is certainly a robust framework, but no tool is without flaws. One area that often frustrates developers is writing integration tests for templates and component classes. The built-in TestBed module leaves much to be desired in terms of developer experience. Tasks that should be straightforward often turn into verbose, awkward code:

  • mocking component dependencies
  • working with a limited set of assertions (only basic Jasmine ones)
  • locating elements via fixture.debugElement — tedious, to say the least
  • simple checks, like whether a button is disabled

In my view, these operations are neither simple nor intuitive. And that's exactly where Spectator steps in:

spectator

Spectator is a well-crafted layer built on top of the standard Angular testing module. It was created by Netanel Basal, who also maintains an excellent Angular blog at https://netbasal.com. With this library, writing, reading, and maintaining tests becomes noticeably easier.

Here are the key benefits it brings to the table:

  • supports testing of pipes, components, directives, HTTP services, and routing
  • simplified DOM element lookup (jQuery-style)
  • rich API for dispatching events, including mouse and keyboard helpers
  • comfortable setup with a HostComponent
  • built-in support for testing ng-content
  • additional matchers like .toBeDisabled() and .toBeFocused()
  • works with entry components and component-level providers
  • automatic service resolution — a real time-saver
  • supports Jest and other test runners
  • global queries available out of the box:
const element = spectator.query('.overlay', {
  root: true,
})

Putting it to the test

Let's see how Spectator compares to the traditional TestBed approach in a few straightforward examples.

  1. Checking if a button is disabled:
 // SPECTATOR
  expect(spectator.query('.button')).toBeDisabled();

  // TEST BED
  const button = fixture.debugElement.query(By.css('.button'));
  expect(button.nativeElement.disabled).toBeTruthy();

2. Verifying that *ngFor renders two components:

  // SPECTATOR
  expect('app-tshirt-item').toHaveLength(2);

  // TEST BED
  const items = fixture.debugElement.queryAll(By.directive(TshirtItemComponent));
  expect(items.length).toBe(2);

3. Mocking a service that the component depends on:

// SPECTATOR
  providers: [mockProvider(AuthService)], // mockProvider ze Spectator

  // TEST BED
  providers: [
    {
      provide: AuthService,
      useClass: MockAuthService, // nasza zamockowana klasa
    },
  ]

4. Simulating an "Enter" key press on an input field:

 // SPECTATOR
  const input = spectator.query('input');
  spectator.keyboard.pressEnter(input);

  // TEST BED
  const input = fixture.debugElement.query(By.css('input'));
  input.triggerEventHandler('keydown.enter', {});

5. Confirming that an element contains expected text:

// SPECTATOR
const title = spectator.query('h1');
expect(title).toContainText('Page title');

// TEST BED
const title = fixture.debugElement.query(By.css('h1'));
expect(title.nativeElement.textContent).toContain('Page title');

There's no doubt that tests written with Spectator are far cleaner and more readable. The official documentation is full of examples worth exploring: https://github.com/ngneat/spectator.

Getting started

If you're ready to try Spectator, the setup is simple. Install it along with ng-mocks using npm:

npm i -D @netbasal/spectator ng-mocks

Schematics support

Spectator also comes with Schematics, which makes scaffolding test files effortless. For instance, to generate a test file for a component, you can run:

ng g cs path/component-name

One more thing

If you develop in Visual Studio Code, don't miss the snippet plugin — it speeds up test authoring considerably:

Spectator – when testing becomes a pleasure — figure 2

You can also experiment with the official playground provided by the authors:

PLAYGROUND

Happy testing!