Angular Material/Jasmine 测试 - 加载时出错 Material 线束

Angular Material/Jasmine testing - errors while loading Material Harnesses

我正在尝试测试使用 Angular Material 构建的组件,但是我在使用 Harness Loader as per documentation (section 'Getting started'.) 初始化 Material 元素时遇到问题。我想把初始化它们的逻辑提取到测试方法之外,使它们更简洁,但似乎行不通。

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);
    });
}));

loader.getHarness() 上的 await 导致很多错误,似乎是 '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>]

我也尝试过 运行 使用全局 async 函数(语法如下:)

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

我什至尝试将这些线束提取到单独的函数中,以尽可能晚地调用它们,但效果也不佳:

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

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

正如 this post 所讨论的,'double-async' 结构是有效的,尽管有点笨拙。但是,它对我不起作用;唯一的变体是 beforeEach(async( () => { ... } ));。是否可以在异步区域 beforeEach 内使用异步等待,或者我是否坚持使用 Promises 手动处理所有内容?

编辑:类似的问题不仅出现在 beforeEach() 中,而且出现在测试方法本身中,即使我没有预初始化线束:

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();*/
    });

当我只取消注释第一个注释行时,代码中断并且测试未通过。 当我包含异步区域时,测试通过,但错误仍然存​​在。我最初认为这是初始化组件的问题,但现在看来它与 HarnessLoader 更相关。

编辑 2:Coderer 的回答链接到 karma.conf.js 的一些问题,所以这里是我的一些配置文件:

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:(如所述导入 here 也没有用)

// 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"
    ]
}

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": []
            }
        }
    }
}

如果您看到关于 ProxyZone 的错误,您可能以错误的顺序加载了区域依赖项。 this GitHub issue but honestly they just did a pretty bad job of documenting how to set up your own tests with Zone. (The current testing intro page 中有一些细节说“CLI 会为您处理 Jasmine 和 Karma 配置。”,这一定很好......如果您使用 CLI 创建项目。)

我遇到了和你一样的错误,把它放在我的 karma.conf.ts:

中就过去了
        // list of files / patterns to load in the browser
        files: [
            // Everybody needs Zone, probably
            "node_modules/zone.js/bundles/zone.umd.js",
            "node_modules/zone.js/bundles/zone-patch-rxjs-fake-async.umd.js",
            "node_modules/zone.js/bundles/zone-testing.umd.js",
            { pattern: "./src/app/**/*.spec.ts", type: "js" }
        ],

认为 如果你不包含 patch-rxjs-fake-async,或者如果你在 zone-testing 之后导入它,你的错误就会出现,但我不能肯定地说。

我提交了问题 21632 about this for Angular 11, because the setup is as it is described in the documentation's Getting started。罪魁祸首是 beforeEach 函数,存在三种可能的解决方案。

选项 1

async 函数已 deprecated and was replaced with waitForAsync. Using waitForAsync in the beforeEach instead of an asynchronous function is one way to resolve the issue as of Angular 11 (demo)。

beforeEach(waitForAsync(() => {
  TestBed.configureTestingModule({
    imports: [MaterialModule, BrowserAnimationsModule, ReactiveFormsModule],
    declarations: [RegistrationComponent]
  }).compileComponents().then(async () => {
    fixture = TestBed.createComponent(RegistrationComponent);
    loader = TestbedHarnessEnvironment.loader(fixture);
    usernameFormField = await loader
      .getHarness(MatFormFieldHarness.with({selector: '#username-form-field'}))
    registrationButton = await loader.getHarness(MatButtonHarness);
  });
}));

选项 2

在没有 waitForAsync 的情况下将异步函数传递给 beforeEach,也适用于您的特定情况 (demo). However, when not handling the compileComponents promise, as most documentation does, move the call to TestbedHarnessEnvironment.loader to the it function (demo)。

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [MatButtonModule],
    declarations: [ButtonHarnessExample]
  }).compileComponents();
  fixture = TestBed.createComponent(ButtonHarnessExample);
});

it('should click a button', async () => {
  const loader = TestbedHarnessEnvironment.loader(fixture);
  const button = await loader.getHarness(MatButtonHarness.with({text: 'Basic button'}));
});

选项 3

在 tsconfig.json (demo) 中将目标更改为 ES2016。

"compilerOptions": {
  "target": "es2016"
}