Class ComponentContext<T>

Provides the foundation for an opinionated pattern for component tests.

  • Includes all features from AngularContext
  • Automatically creates your component at the beginning of run().
  • Sets up Angular to call ngOnChanges() like it would in production. This is not the case if you use the standard TestBed.createComponent() directly.
  • Wraps your component in a parent that you can easily style however you like.
  • Lets you use component harnesses in the fakeAsync zone, which is normally a challenge.
  • Automatically disables animations.
  • Causes async APP_INITIALIZERs to complete before instantiating the component. Two caveats:
    • this requires all work in your initializers to complete with a call to tick()
    • this requires delaying app initialization until inside the fakeAsync zone, i.e. with the callback to #run. If you have async initializers, you must be careful not to do things that finalize the app setup before then, such as #inject.

A very simple example:

@Component({ standalone: true, template: 'Hello, {{name}}!' })
class GreeterComponent {
@Input() name!: string;
}

it('greets you by name', () => {
const ctx = new ComponentContext(GreeterComponent);
ctx.assignInputs({ name: 'World' });
ctx.run(() => {
expect(ctx.fixture.nativeElement.textContent).toBe('Hello, World!');
});
});

A full example, with routing and a component harness. This is the full code for a tiny Angular app:

 /////////////////
// app-context.ts

// To re-use your context setup, make a subclass of ComponentContext to import into any spec
class AppContext extends ComponentContext<AppComponent> {
constructor() {
super(AppComponent, {
// Import `routes` from `app.routes.ts`
imports: [RouterTestingModule.withRoutes(routes)],
// Import `appConfig` from `app.config.ts`
providers: appConfig.providers,
});
}
}

////////////////////////
// app.component.spec.ts

describe('AppComponent', () => {
let ctx: AppContext;
beforeEach(() => {
ctx = new AppContext();
});

it('can navigate to the first page', () => {
ctx.run(async () => {
const app = await ctx.getHarness(AppComponentHarness);
await app.navigateToFirstPage();
expect(ctx.fixture.nativeElement.textContent).toContain(
'First works!',
);
});
});
});

///////////////////////////
// app.component.harness.ts

// A simple component harness to demonstrate its integration with component contexts
class AppComponentHarness extends ComponentHarness {
static hostSelector = 'app-root';

#getFirstPageLink = this.locatorFor('a');

async navigateToFirstPage(): Promise<void> {
const link = await this.#getFirstPageLink();
await link.click();
}
}

/////////////////////
// first.component.ts

// A minimal component for demonstration purposes
@Component({ standalone: true, template: '<p>First works!</p>' })
class FirstComponent {}

///////////////////
// app.component.ts

// A minimal app component with routing for demonstration purposes
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink],
template: `
<a routerLink="/first-page">First Page</a>
<router-outlet />
`,
})
class AppComponent {}

////////////////////////
// app.routes.ts

const routes: Routes = [{ path: 'first-page', component: FirstComponent }];

////////////////
// app.config.ts

const appConfig: ApplicationConfig = { providers: [provideRouter(routes)] };

Type Parameters

  • T

Hierarchy (view full)

Constructors

Properties

fixture: ComponentFixture<unknown>

The ComponentFixture for a synthetic wrapper around your component. Available with the callback to run().

startTime: Date = ...

Set this before calling run() to mock the time at which the test starts.

Methods

  • Assign inputs passed into your component. Can be called before run() to set the initial inputs, or within run() to update them and trigger all the appropriate change detection and lifecycle hooks.

    Parameters

    • inputs: Partial<T>

    Returns void

  • Assign css styles to the div wrapping your component. Can be called before or during run(). Accepts an object with the same structure as the ngStyle directive.

    ctx.assignWrapperStyles({
    width: '400px',
    height: '600px',
    margin: '20px auto',
    border: '1px solid',
    });

    Parameters

    • styles: Record<string, any>

    Returns void

  • Runs test in a fakeAsync zone. It can use async/await, but be sure anything you await is already due to execute (e.g. if a timeout is due in 3 seconds, call .tick(3000) before awaiting its result).

    Also runs the following in this order, all within the same zone:

    1. this.init()
    2. test()
    3. this.verifyPostTestConditions()
    4. this.cleanUp()

    Parameters

    • test: (() => void | Promise<void>)
        • (): void | Promise<void>
        • Returns void | Promise<void>

    Returns void