Angular15+Cypress12组件测试:index.ts导入服务失败求助
问题描述
技术栈:Angular v15、Cypress v12
在组件中通过services目录的index.ts批量导入服务时,Cypress组件测试失败;直接导入具体文件路径则测试正常,希望通过tsconfig或Cypress配置解决该问题。
相关代码
示例组件
import { Component } from '@angular/core'; import { UserHttp } from '../../services'; @Component({ selector: 'example-view', templateUrl: './example.component.html', }) export class ExampleComponent { constructor( private userHttp: UserHttp, ) { } }
组件测试代码
import { HttpClientModule } from '@angular/common/http'; import { ExampleComponent } from './example.component'; import { UserHttp } from '../../services'; describe('Example component', () => { beforeEach(() => { cy.mount(ExampleComponent, { providers: [UserHttp], imports: [HttpClientModule] }); }); it('should display default title', () => { cy.get('h2').should('exist'); }); });
UserHttp服务
import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; @Injectable() export class UserHttp { test(): Observable<any> { return of({}); } }
services目录index.ts(批量导出)
// index.ts: services: import { UserHttp } from './http/user.http'; import { StorageService } from './storage.service'; export * from './http/user.http'; export * from './storage.service'; export const SERVICES = [ StorageService, UserHttp, ];
StorageService服务
import { Injectable } from '@angular/core'; import { environment } from '../../environments/environment'; @Injectable() export class StorageService { baseKey: string = environment.baseStorageKey; constructor() {} setLocalStorage(key: string, value: any): void { this.removeLocalStorage(key); localStorage.setItem(`${this.baseKey}${key}`, JSON.stringify(value)); } getLocalStorage(key: string): any { const item = localStorage.getItem(`${this.baseKey}${key}`); return item !== null ? JSON.parse(item) : ''; } removeLocalStorage(key: string): void { localStorage.removeItem(`${this.baseKey}${key}`); } removeBatchLocalStorage(keys: string[]): void { keys.forEach((key: string) => { localStorage.removeItem(`${this.baseKey}${key}`); }); } }
Cypress配置文件
import { defineConfig } from "cypress"; export default defineConfig({ e2e: { setupNodeEvents(on, config) { // implement node event listeners here }, }, chromeWebSecurity: false, screenshotsFolder: "cypress/snapshots", trashAssetsBeforeRuns: true, viewportWidth: 1400, viewportHeight: 1200, video: false, env: { local: "http://localhost:4200/", staging: "https://hidden.co.uk/", user: { email: "hidden", password: "hidden", }, }, component: { devServer: { framework: "angular", bundler: "webpack", }, specPattern: "**/*.cy.ts", }, });
TypeScript配置文件
{ "compileOnSave": false, "compilerOptions": { "baseUrl": "./", "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "strict": false, "strictPropertyInitialization": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, "downlevelIteration": true, "experimentalDecorators": true, "esModuleInterop": true, "emitDecoratorMetadata": true, "moduleResolution": "node", "importHelpers": true, "target": "es2020", "module": "es2020", "skipLibCheck": true, "allowJs": true, "types": [ "node" ], "lib": [ "es2018", "dom" ], "paths": { "@app/*": ["src/app/*"], "@services/*": ["src/app/services/*"] } }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, "strictInputAccessModifiers": false, "strictTemplates": false } }
Http基类(UserHttp继承的类)
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { environment } from '../../../environments/environment'; @Injectable() export abstract class Http { protected baseUrl = environment.baseUrl; protected headers: HttpHeaders; constructor(public httpClient: HttpClient) {} protected get<T>(path: string, options: any = {}, noBaseUrl = false): Observable<T> { const url: string = this.createUrlString(path, noBaseUrl); const params: HttpParams = this.getHttpParams(options.queryString); return this.httpClient.get<T>(url, { params }); } protected post<T>(path: string, data = {}, noBaseUrl = false): Observable<T> { const url: string = this.createUrlString(path, noBaseUrl); const options = { headers: this.headers }; return this.httpClient.post<T>(url, { ...data, lang: 'uk' }, options); } protected createUrlString(resourcePath: string, noBaseUrl: boolean): string { return noBaseUrl ? `${resourcePath}` : `${this.baseUrl}${resourcePath}`; } protected getHttpParams(params: any): HttpParams { let httpParams: HttpParams = new HttpParams(); if (params) { for (const prop in params) { if (params.hasOwnProperty(prop)) { const parameterValue: string = params[prop].toString(); httpParams = httpParams.append(prop, parameterValue); } } } return httpParams; } }
当前问题与报错
当在组件中使用import { UserHttp } from '../../services'(通过index.ts批量导入)时,Cypress测试抛出以下错误:
TypeError The following error originated from your test code, not from Cypress. > Cannot read properties of undefined (reading 'StorageService') When Cypress detects uncaught errors originating from your test code it will automatically fail the current test. Cypress could not associate this error to any specific test. We dynamically generated a new test to display this failure.
但如果改为直接导入具体文件路径import { UserHttp } from '../../services/http/user.http',测试则正常运行。
解决方案
可以通过以下两种配置方式解决批量导入的测试问题:
1. 优化TypeScript路径映射
在tsconfig.json的paths中添加直接指向services目录index的映射,确保Cypress能正确解析批量导出的模块:
"paths": { "@app/*": ["src/app/*"], "@services": ["src/app/services/index"], "@services/*": ["src/app/services/*"] }
之后在组件和测试中使用import { UserHttp } from '@services'导入,避免相对路径的解析问题。
2. 配置Cypress的webpack解析规则
在Cypress配置中扩展webpack配置,添加对services目录index的解析支持:
修改cypress.config.ts的component配置部分:
import path from 'path'; import { defineConfig } from "cypress"; export default defineConfig({ // ...其他配置 component: { devServer: { framework: "angular", bundler: "webpack", webpackConfig: { resolve: { alias: { '@services': path.resolve(__dirname, 'src/app/services/index.ts') } } } }, specPattern: "**/*.cy.ts", }, });
3. 额外注意事项
- 确保
services/index.ts中没有循环导入的情况,循环导入会导致模块解析时出现undefined的问题; - 检查
StorageService的导入是否存在路径错误,确保在index.ts中的导入路径正确。
内容的提问来源于stack exchange,提问作者AngularM
相关产品推荐
相关产品推荐

