angularunit-testingkarma-jasmineangular-unit-testangular-zoneless

Setup global providers for Angular unit tests


I have generated a fresh Angular 18 project and made it zoneless. It works fine, however in all of my unit tests I am now getting the following error:

Error: NG0908: In this configuration Angular requires Zone.js

I resolved the error by adding provideExperimentalZonelessChangeDetection() to each TestBed's providers, however it's quite annoying needing to add this manually to every spec file. Also it pops up again every time I generate a new component.

Is there a way to set up this provider globally for all unit tests? I have read that in the src/test.ts file it is possible to globally configure the tests, but in my project this file does not exist.


Solution

  • There are to approaches to solve this issue:

    1. Migrate to the unit-test builder (experimental) and add a providersFile
    2. Setup a test.ts file

    1. Setup a providersFile in the unit-test builder (experimental)

    In Angular v20, experimental support for a new unit testing system was added. The main goal of this system is to provide support for other unit test runners such as vitest. But it also has a feature that helps setting up global providers.

    Create a new file src/test-providers.ts and export an array with the providers that you want to have in all unit tests:

    import { provideZonelessChangeDetection } from '@angular/core';
    
    export default [provideZonelessChangeDetection()];
    

    Now update your angular.json and set the providersFile option of the unit-test runner:

    {
      "projects": {
        "your-project": {
          "architect": {
            "test": {
              "builder": "@angular/build:unit-test",
              "options": {
                "buildTarget": "::development",
                "runner": "vitest",
                "providersFile": "src/providers.ts",
                "tsConfig": "tsconfig.spec.json"
              }
            }
          }
        }
      }
    }
    

    Finally, the file needs to be added to to the files of the tsconfig.spec.json:

    {
      "extends": "./tsconfig.json",
      "compilerOptions": {
        "outDir": "./out-tsc/spec",
        "types": ["@angular/localize"]
      },
      "files": ["src/providers.ts"],
      "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
    }
    

    2. Setup a test.ts file

    Older Angular projects used to have a test.ts file where the modules for all tests can be configured. Apparently in newer Angular projects, this file is not automatically generated anymore. However, it can be created manually.

    The next challenge is that initTestEnvironment() only accepts modules, no providers. To solve this issue, I created a module with a provider:

    import {
      NgModule,
      provideZonelessChangeDetection,
    } from '@angular/core';
    import { getTestBed } from '@angular/core/testing';
    import {
      BrowserDynamicTestingModule,
      platformBrowserDynamicTesting,
    } from '@angular/platform-browser-dynamic/testing';
    
    @NgModule({
      // pre Angular v20, use provideExperimentalZonelessChangeDetection
      providers: [provideZonelessChangeDetection()],
    })
    class ZonelessModule {}
    
    getTestBed().initTestEnvironment(
      [BrowserDynamicTestingModule, ZonelessModule],
      platformBrowserDynamicTesting(),
    );
    

    Now this file needs to be registered in the angular.json via the main option:

    {
      "projects": {
        "my-project": {
          "architect": {
            // ...
            "test": {
              "builder": "@angular-devkit/build-angular:karma",
              "options": {
                "main": "src/test.ts",
                "tsConfig": "tsconfig.spec.json",
                "inlineStyleLanguage": "scss",
                "assets": [
                  {
                    "glob": "**/*",
                    "input": "public"
                  }
                ],
                "styles": ["src/styles.scss"],
                "scripts": [],
                "polyfills": ["@angular/localize/init"]
              }
            }
          }
        }
      }
    }
    

    Finally, the file needs to be added to to the files of the tsconfig.spec.json:

    {
      "extends": "./tsconfig.json",
      "compilerOptions": {
        "outDir": "./out-tsc/spec",
        "types": ["jasmine", "@angular/localize"]
      },
      "files": ["src/test.ts"],
      "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
    }