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

Posted

技术标签:

【中文标题】Angular Material/Jasmine 测试 - 加载 Material Harnesses 时出错【英文标题】:Angular Material/Jasmine testing - errors while loading Material Harnesses 【发布时间】:2020-11-23 21:37:52 【问题描述】:

我正在尝试测试使用 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 所讨论的,“双异步”结构是有效的,虽然有点笨拙。但是,它对我不起作用。唯一的变体是beforeEach(async( () =&gt; ... ));。是否可以在异步区域的beforeEach 内使用 async-await,还是我坚持使用 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": []
            
        
    

【问题讨论】:

你找到答案了吗?我注意到,如果我不包含 zone-patch-rxjs-fake-async,“预计在 ProxyZone”中的错误就会消失,但对 loader.getAllHarnesses 的调用永远不会解决。 请参阅下面关于以正确顺序包含zone-patch-etc 的答案,而且,我对loader.getAllHarnesses 的调用实际上 未能解决,因为他们在下面调用fixture.whenStable()引擎盖,如果被测组件完全使用setIntervalwhenStable 永远不会解析。我还在戳那个。 嘿,我现在才注意到你的回答。我过会儿去看看,然后告诉你。 【参考方案1】:

我为 Angular 11 提交了关于此的问题 21632,因为设置与文档的 Getting started 中描述的一样。罪魁祸首是beforeEach 函数,有三种可能的解决方案。

选项 1

async 函数已被 deprecated 替换为 waitForAsync。从 Angular 11 (demo) 开始,在 beforeEach 中使用 waitForAsync 而不是异步函数是解决问题的一种方法。

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)。但是,当不处理 compileComponents 承诺时,就像大多数文档所做的那样,将对 TestbedHarnessEnvironment.loader 的调用移动到 it 函数 (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"

【讨论】:

非常感谢您的回答!降级目标就足够了;显然,我没想到这可能是原因,尽管 CLI 警告 async/await 在较新的目标中存在潜在问题。 将目标从 es2017 更改为 es2016 也对我有用【参考方案2】:

如果您看到有关 ProxyZone 的错误,则可能是您的区域依赖项以错误的顺序加载。 this GitHub issue 中有一些细节,但老实说,他们在记录如何使用 Zone 设置您自己的测试方面做得非常糟糕。 (当前的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 之后导入它,您的错误就会出现,但我不能肯定地说。

【讨论】:

感谢您的回答!不幸的是,在karma.conf.jstest.ts 中导入这些文件并没有帮助(然后,实际上没有任何测试通过)。我用一些(可能)相关的配置文件更新了这个问题;如果你能看看它们,我将不胜感激。 我是否正确理解包含zone-patch-rxjs-fake-async 会导致所有测试失败?你得到什么错误信息?我不确定我能提供多少帮助,除了说没有它我得到了“预计在 ProxyZone”错误,而现在我没有。

以上是关于Angular Material/Jasmine 测试 - 加载 Material Harnesses 时出错的主要内容,如果未能解决你的问题,请参考以下文章

从“@angular”而不是“angular2”导入 *

找不到模块'angular2/angular2'

Angular 6 和业力'无法加载“@angular-devkit/build-angular”,它未注册'

angular.js 的angular.copy angular.extend angular.merge

如何使用 @angular/upgrade 在 Angular 1 应用程序中使用 Angular 2 组件

Angular 1 和 Angular 2 集成:无缝升级的方法