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( () => ... ));
。是否可以在异步区域的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()
引擎盖,如果被测组件完全使用setInterval
,whenStable
永远不会解析。我还在戳那个。
嘿,我现在才注意到你的回答。我过会儿去看看,然后告诉你。
【参考方案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.js
或test.ts
中导入这些文件并没有帮助(然后,实际上没有任何测试通过)。我用一些(可能)相关的配置文件更新了这个问题;如果你能看看它们,我将不胜感激。
我是否正确理解包含zone-patch-rxjs-fake-async
会导致所有测试失败?你得到什么错误信息?我不确定我能提供多少帮助,除了说没有它我得到了“预计在 ProxyZone”错误,而现在我没有。以上是关于Angular Material/Jasmine 测试 - 加载 Material Harnesses 时出错的主要内容,如果未能解决你的问题,请参考以下文章
Angular 6 和业力'无法加载“@angular-devkit/build-angular”,它未注册'
angular.js 的angular.copy angular.extend angular.merge