Angular Material/Jasmine testing - errors while loading Material Harnesses

Discussão em 'Angular' iniciado por Kamil Plich, Julho 15, 2025.

  1. Kamil Plich

    Kamil Plich Guest

    I'm trying to test components built with Angular Material, however I'm encountering a problem initializing Material elements using Harness Loader as per documentation (section 'Getting started'.). I'd like to extract the logic of initializing them outside of the test methods to make them more concise, but it doesn't seem to work.

    Within describe():

    let usernameFormField: MatFormFieldHarness;
    let registrationButton: MatButtonHarness;

    beforeEach(async(() => {
    TestBed.configureTestingModule({
    imports: [MaterialModule, BrowserAnimationsModule, ReactiveFormsModule],
    declarations: [RegistrationComponent],
    providers: [ /*provide spies */ ]
    }).compileComponents().then(async () => {
    fixture = TestBed.createComponent(RegistrationComponent);
    loader = TestbedHarnessEnvironment.loader(fixture);

    // what works, but I don't like
    /*loader.getHarness(
    MatFormFieldHarness.with({selector: '#username-form-field'})
    ).then(harness => {
    usernameFormField = harness;
    });*/

    // what doesn't work
    usernameFormField = await loader
    .getHarness(MatFormFieldHarness.with({selector: '#username-form-field'}))

    // other form elements

    // to my confusion, this works without any problem
    registrationButton = await loader.getHarness(MatButtonHarness);
    });
    }));


    The await on loader.getHarness() causes lots of errors, seemingly about code not running in 'ProxyZone'.

    context.js:265 Unhandled Promise rejection: Expected to be running in 'ProxyZone', but it was not found. ; Zone: <root> ; Task: Promise.then ; Value: Error: Expected to be running in 'ProxyZone', but it was not found.
    at Function.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.assertPresent (zone-testing.js:210) [<root>]
    at Function.setup (testbed.js:61) [<root>]
    at new TestbedHarnessEnvironment (testbed.js:572) [<root>]
    at TestbedHarnessEnvironment.createEnvironment (testbed.js:633) [<root>]
    at TestbedHarnessEnvironment.createComponentHarness (testing.js:341) [<root>]
    at TestbedHarnessEnvironment.<anonymous> (testing.js:384) [<root>]
    at Generator.next (<anonymous>) [<root>]
    at :9876/_karma_webpack_/node_modules/tslib/tslib.es6.js:74:1 [<root>]
    at new ZoneAwarePromise (zone-evergreen.js:960) [<root>]
    at __awaiter (tslib.es6.js:70) [<root>]
    at TestbedHarnessEnvironment._getQueryResultForElement (testing.js:379) [<root>]
    at :9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing.js:366:1 [<root>]
    at Array.map (<anonymous>) [<root>]
    at TestbedHarnessEnvironment.<anonymous> (testing.js:366) [<root>] Error: Expected to be running in 'ProxyZone', but it was not found.
    at Function.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.assertPresent (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:210:1) [<root>]
    at Function.setup (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing/testbed.js:61:1) [<root>]
    at new TestbedHarnessEnvironment (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing/testbed.js:572:1) [<root>]
    at TestbedHarnessEnvironment.createEnvironment (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing/testbed.js:633:1) [<root>]
    at TestbedHarnessEnvironment.createComponentHarness (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing.js:341:1) [<root>]
    at TestbedHarnessEnvironment.<anonymous> (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing.js:384:1) [<root>]
    at Generator.next (<anonymous>) [<root>]
    at http://localhost:9876/_karma_webpack_/node_modules/tslib/tslib.es6.js:74:1 [<root>]
    at new ZoneAwarePromise (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:960:1) [<root>]
    at __awaiter (http://localhost:9876/_karma_webpack_/node_modules/tslib/tslib.es6.js:70:1) [<root>]
    at TestbedHarnessEnvironment._getQueryResultForElement (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing.js:379:25) [<root>]
    at http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing.js:366:1 [<root>]
    at Array.map (<anonymous>) [<root>]
    at TestbedHarnessEnvironment.<anonymous> (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/fesm2015/testing.js:366:1) [<root>]


    I'd also tried running this with a global async function (with the following syntax:)

    beforeEach(async( async () => {
    // magic happening here
    }));


    I even tried extracting these harnesses into separate functions, to call them as late as possible, but it also didn't work well:

    const usernameFormField = () => {
    loader.getHarnes(...);
    }

    // later in code; not the most elegant, but good enough
    const usernameField = await usernameFormField();
    expect(await usernameField().hasErrors()).toBeFalsy();


    As this post discusses, the 'double-async' construct is valid, if a little clumsy. However, it didn't work for me; the only variant that did was beforeEach(async( () => { ... } ));. Is it possible to use async-await inside beforeEach in async zone, or am I stuck with handling everything manually using Promises?

    EDIT: a similar problem shows up not only in beforeEach(), but also in test methods themselves, even when I don't preinitialize the harnesses:

    it('should display \'log out\' and \'my account\' buttons when user is authenticated',
    async () => {
    const EXAMPLE_USERNAME = 'username';

    spyOnProperty(authenticationService, 'authenticatedUser')
    .and.returnValue(EXAMPLE_USERNAME);

    expect(fixture.componentInstance.authenticatedUser)
    .toEqual(EXAMPLE_USERNAME);

    const logOutButton = await loader
    .getHarness(MatButtonHarness.with({text: BUTTON_LOG_OUT_TEXT}));
    expect(await logOutButton.isDisabled()).toBeFalsy();

    // the following line causes a problem
    /*const myAccountButton = await loader
    .getHarness(MatButtonHarness.with({text: BUTTON_MY_ACCOUNT_TEXT}));
    expect(await myAccountButton.isDisabled()).toBeFalsy();
    await myAccountButton.click();
    expect(routerSpy.navigateByUrl).toHaveBeenCalled();*/
    });


    When I uncomment only the first commented line, the code breaks and the test doesn't pass. When I include the async zone, the test passes, but the errors persist. I initially thought this is a problem with initializing the component, but now it seems it's more related to HarnessLoader.

    EDIT 2: Coderer's answer links to some problems with karma.conf.js, so here are some of my configuration files:

    karma.conf.js:

    // custom headless chrome from
    // https://coryrylan.com/blog/building-angular-cli-projects-with-github-actions

    module.exports = function (config) {
    config.set({
    // adding any files here didn't seem to work
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
    require('karma-jasmine'),
    require('karma-chrome-launcher'),
    require('karma-jasmine-html-reporter'),
    require('karma-coverage-istanbul-reporter'),
    require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
    clearContext: false, // leave Jasmine Spec Runner output visible in browser
    jasmine: {
    random: false
    }
    },
    coverageIstanbulReporter: {
    dir: require('path').join(__dirname, './coverage/elx-front-end'),
    reports: ['html', 'lcovonly', 'text-summary'],
    fixWebpackSourcePaths: true
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
    restartOnFileChange: true,
    customLaunchers: {
    ChromeHeadlessCI: {
    base: 'ChromeHeadless',
    flags: ['--no-sandbox', '--disable-gpu']
    }
    }
    });
    };


    src/test.ts: (importing as described here also didn't work)

    // This file is required by karma.conf.js and loads recursively all the .spec and framework files

    import "zone.js/dist/zone-testing";
    import {getTestBed} from "@angular/core/testing";
    import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";

    declare const require: any;

    // First, initialize the Angular testing environment.
    getTestBed().initTestEnvironment(
    BrowserDynamicTestingModule,
    platformBrowserDynamicTesting()
    );
    // Then we find all the tests.
    const context = require.context("./", true, /\.spec\.ts$/);
    // And load the modules.
    context.keys().map(context);


    tsconfig.base.json:

    {
    "compileOnSave": false,
    "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "es2020",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "importHelpers": true,
    "target": "es2018",
    "strict": true,
    "typeRoots": [
    "node_modules/@types"
    ],
    "lib": [
    "es2018",
    "dom"
    ],
    "paths": { /* custom paths */}
    },
    "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true
    }
    }


    tsconfig.spec.json:

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


    fragment of angular.json:

    "architect": {
    ...
    "test": {
    "builder": "@angular-devkit/build-angular:karma",
    "options": {
    "main": "src/test.ts",
    "polyfills": "src/polyfills.ts",
    "tsConfig": "tsconfig.spec.json",
    "karmaConfig": "karma.conf.js",
    "assets": [
    "src/favicon.ico",
    "src/assets"
    ],
    "styles": [
    "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
    "src/styles.scss",
    "src/dimens.scss"
    ],
    "scripts": []
    }
    }
    }
    }

    Continue reading...

Compartilhe esta Página