Unit Testing in Angular 15 Without TestBed

Bridging the gap between constructor-based DI and inject-based DI testing without TestBed

Edward Ezekiel
HeroDevs

--

A colorful bridge

Angular 14 introduced the ability to use the inject function in classes like components, directives, and pipes. Library authors have embraced this feature and many have dropped constructor-based Dependency Injection (’DI’). It also inspired a reusable functions called DI Functions.

There are lots of approaches using TestBed that allow to you test and even mock dependencies provided by the Inject function.

However, what if you don’t want to use TestBed? Maybe because of performance concerns, maybe you prefer isolating unit tests from the DOM by default, or maybe you’ve noticed that Angular is introducing utilities to make it easier to run functions in an injection context with providers.

tl;dr:

  • run npm i @ngx-unit-test/inject-mocks -D to install utility functions to unit test any Angular class or function without TestBed.

There are simple approaches to mocking providers using constructor-based DI without TestBed but no clear guide bridging the gap between constructor-based DI and inject-based DI testing without TestBed.

This article (1) summarizes a Component that needs Unit Testing, (2) demonstrates how to test that Component in Angular 15+ without TestBed, and (3) shows how to test a DI Function without TestBed.

1. The Setup: A Component That Needs Unit Testing

Here is a simple HomeComponent written in Angular 15 that uses the inject function:

The component injects a WidgetsFacade, accesses some observables exposed by that facade, and triggers a facade method during ngOnInit.

2. Providing Mock Dependencies

So, how could you mock the injected WidgetsFacade without using TestBed?

If you just run the spec without TestBed you get this error:

Error NG0203 thrown when testing a Component without providing dependencies

In order to provide the service and mock its functions there are two steps:

First, create a utility method that wraps the @angular/core Injector:

This utility leverages Angular’s built-in Injector class to automatically provide dependencies to a given class. You may notice the name is classWithProviders and not componentWithProviders. That is because the utility works with Components, Services, Directives, and Pipes!

Second, use classWithProviders in a spec file:

Let’s walk through each step in the spec file:

  1. Declare a variable facadeMock typed as Partial<WidgetsFacade>. Use Partial to get type inference in the next step.
  2. Assign mock object: create a JavaScript object and assign jest.fn() to the method you need to mock.
  3. Get Component with Injection Context: Thanks to the classWithProviders util, the returned component has the mocked provider!
FacadeMock is provided to HomeComponent as the WidgetsFacade

3. Testing DI Functions Without TestBed

One limitation of classWithProviders is that it does not work with Dependency Injection Functions (DI Functions). The current solution for testing DI Functions involves TestBed.runInInjectionContext, which was released in Angular 15.1.0.

Building on TestBed’s example, it is possible to create a standalone utility that provides a similar solution for testing DI Functions with mocked providers:

In fact, the Angular team is already working on a new runInInjectionContext utility that replaces and extends the runInContext method.

Conclusion

The classWithProviders and runFnInContext utilities were inspired by code from the Angular.io source code. I’ve taken the extra step of bundling these utilities into an npm package. Thanks to Josh Van Allen and Rafael Mestre for their insight and help developing these utilities.

About HeroDevs

HeroDevs is a software engineering and consulting studio that specializes in front-end development. Our team has authored or co-authored projects like the Angular CLI, Angular Universal, Scully, XLTS — extended long-term support for AngularJS, Vue2, Protractor, and many others. We work with fast-growing startups and some of the world’s largest companies like Google, GE, Capital One, Experian, T-Mobile, Corteva, and others. Learn more about us at herodevs.com.

--

--

💻 Senior Angular Engineer @herodevs | 👨‍👩‍👧‍👦 Husband and Dad | ⚖️ Juris Doctor