Thought Leadership
Apr 11, 2023

Unit Testing in Angular 15 Without TestBed

Bridging the gap between constructor-based DI and inject-based DI testing without TestBed
Unit Testing in Angular 15 Without TestBed

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:

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!

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.iosource 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 partners with open-source authors to offer comprehensive solutions for sunsetted open-source software. Our Never-Ending Support products ensure businesses remain secure and compliant, even as their depended-upon open-source packages reach end-of-life. Alongside this, our elite team of software engineers and architects provides expert consulting and engineering services, assisting clients in migrating from deprecated packages and modernizing their technology stacks.

Article Summary
Learn how to unit test Angular components and DI functions without TestBed using @ngx-unit-test/inject-mocks. Simplify dependency mocking in Angular 15+.
Author
Edward Ezekiel
Solutions Architect
Related Articles
Executive Order 14028: Elevating National Cybersecurity
The White House's Call to Action for a Safer Digital Future Setting New Benchmarks for Global Cybersecurity Standards
PCI Compliance: What Every Business Owner Needs to Know
Understanding the Essentials of Payment Security and PCI DSS Integration
Navigating Drupal 7 End-of-Life: Your Options and HeroDevs' Never-Ending Support
Explore paths for Drupal 7 users, from upgrading to newer versions to leveraging ongoing support with HeroDevs.