Where there's smoke, there's fire. Cover photo by blickpixel on Pixabay.

The approach shown here relies on TestBed to spin up the entire AppModule untouched, giving us an integrated test that lands very close to a true end-to-end scenario within the constraints of the testing environment.

Putting together the smoke test harness

Let's start by examining the test harness, which needs nothing more than the AppModule and the AppComponent from our application.

// if needed
// import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';

import { AppComponent } from './app.component';
import { AppModule } from './app.module';

function setup() {
  TestBed.configureTestingModule({
    imports: [
      AppModule,
      RouterTestingModule,
      // if needed
      // HttpClientTestingModule,
    ],
  }).compileComponents();

  let rootFixture: ComponentFixture<AppComponent>;
  const initializeRootFixture = () => {
    if (rootFixture == null) {
      rootFixture = TestBed.createComponent(AppComponent);
    }
  };

  return {
    get router() {
      initializeRootFixture();

      return TestBed.inject(Router);
    },
    run<TResult>(task: () => TResult) {
      initializeRootFixture();

      return rootFixture.ngZone == null
        ? task()
        : rootFixture.ngZone.run(task);
    },
  };
}
Enter fullscreen mode Exit fullscreen mode
The setup for our Angular application smoke test.

The AppModule is brought into the Angular testing module, and RouterTestingModule is included to mock the History and Location APIs.

With the shared setup in place, the first smoke test case can now be written.

Confirming the application starts up

This initial test case checks that the application boot sequence completes with no errors.

describe('Applicaton smoke test', () => {
  it('the application boots up', () => {
    const bootApplication = () => {
      const { router, run } = setup();

      run(() => router.initialNavigation());
    };

    expect(bootApplication).not.toThrow();
  });
});
Enter fullscreen mode Exit fullscreen mode
This test case verifies that our Angular application can boot.

Once the setup function loads the AppModule into the Angular testing module, routing is configured and a navigation to the default route is initiated. The expectation here is that no failure is thrown during this process.

Given that the AppModule and all of its configuration and initialization logic are being exercised, this test covers a wide spectrum of Angular APIs interacting with our application.

Checking that routing works

The next test case targets navigation to a given route—in this instance, the default route—thereby also touching any route guards or resolvers in the path.

describe('Applicaton smoke test', () => {
  it('navigation works', async () => {
    const { router, run } = setup();

    const canNavigate = await run(() => router.navigateByUrl('/'));

    expect(canNavigate).toBe(true);
  });
});
Enter fullscreen mode Exit fullscreen mode
This test case verifies that navigation works in our Angular application and that the default route can be reached.

If navigating to the default route is permitted and completes successfully, canNavigate will be true. Should a guard block the path or a resolver throw an error, canNavigate will resolve tofalse`, causing the test to fail.

For applications where most routes sit behind an authentication check, swap router.navigateByUrl('/') for router.navigateByUrl('/login'), or introduce login logic into the test setup.

Wrapping up

Testing pyramid

The Angular testing pyramid.

Integrated tests built on TestBed strike a solid balance between implementation overhead and runtime speed. They are quicker than most end-to-end frameworks, yet they still offer realistic component rendering, all with minimal coding effort. That combination delivers significant value.

Potential side effects to watch for

You may have to supply fake APIs or services to stand in for those that would generate side effects in a live environment. Such effects can come from application initializers, the OnInit hook, or other Angular-triggered events. They are all injected via the Angular testing module that TestBed controls.

Tools like Mock Service Worker can be helpful for stubbing web API endpoints in smoke tests. For deeper guidance, I suggest reading "Using MSW (Mock Service Worker) in an Angular project" by Tim Deschryver and checking out the official Angular examples.

Ideas for expanding the suite

So far, the smoke suite has two cases: one to boot the app without errors, and another to navigate to the default route.

A natural follow-up would be adding cases that target other routes. Even more valuable would be a suite that walks through the application's primary user flows end to end.