You need to enable JavaScript to run this app. Angular使用Jest运行测试报Can't resolve all parameters for CustomTranslationLoader错
优惠活动
大模型
产品
解决方案
定价
更多

Angular使用Jest运行测试报Can't resolve all parameters for CustomTranslationLoader错

问题描述

我正在为登录页面编写第一个测试用例,运行测试时出现两个异常:

  • 抛出错误 Can't resolve all parameters for CustomTranslationLoader: (?)
  • 测试断言 expect(component).toBeTruthy() 失败,返回值为undefined,打印fixture也为undefined

登录组件代码(login.component.ts)

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {AuthenticationService} from '../../../services/authentication.service';
import {HttpErrorResponse} from "@angular/common/http";
import _ from "lodash";

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
})
export class LoginComponent implements OnInit {

  loginForm: FormGroup;
  private returnUrl: string;
  invalidCredentials = false;
  error = '';
  forbiddenUser = false;

  constructor(private formBuilder: FormBuilder,
              private route: ActivatedRoute,
              private router: Router,
              private authenticationService: AuthenticationService) {

  }

  username: string;
  password: string;

  ngOnInit() {
    this.loginForm = this.formBuilder.group({
      username: ['', Validators.required],
      password: ['', Validators.required]
    });
  }

  authenticate() {
    let code;
    let accessToken;
    let refreshToken;
    code = localStorage.getItem('code');
    if (code) {
      code = window.atob(code);
      accessToken = localStorage.getItem('accessToken');
      refreshToken = localStorage.getItem('refreshToken');
      if (accessToken && refreshToken) {
        accessToken = window.atob('accessToken');
        refreshToken = window.atob('refreshToken');
      }
    }
  }

  onSubmit() {
    this.invalidCredentials = false;
    this.forbiddenUser = false;

    this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';

    // stop here if form is invalid
    if (this.loginForm.invalid) {
      return;
    }

    this.authenticationService.login(this.loginForm.value.username, this.loginForm.value.password).subscribe({
      next: (data) => {
        this.router.navigateByUrl(this.returnUrl);
      },
      error: (errRes: HttpErrorResponse) => {
        if (errRes.status === 403) {
          if (errRes.error.ErrorMessage === "loginFailure") {
            this.invalidCredentials = true;
          } else if (_.has(errRes, 'error.username')) {
            this.forbiddenUser = true;
          }
        }
      }
  });
  }

}

测试文件代码(login.component.spec.ts)

import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LoginComponent } from './login.component';
import {AuthenticationService} from '../../../services/authentication.service';
import { createComponent, Page, CommonTestUtilsModule, provideMockObject } from '@webex/common/test-utils';
import { CommonUiMomentumChModule } from '@webex/common/ui/momentum-ch';


const MockAuthenticationService = {
  login: jest.fn(),
};

class LoginComponentPage extends Page<LoginComponent> {
  constructor(fixture: ComponentFixture<LoginComponent>) {
    super(fixture);
  }
}

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>
  let page: LoginComponentPage;
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      providers: [
        provideMockObject(AuthenticationService),
        {
          provide: AuthenticationService,
          useValue: MockAuthenticationService,
        },
      ],
      imports: [
        CommonUiMomentumChModule,
        CommonTestUtilsModule,
        FormsModule,
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    }).compileComponents();
  }));

  beforeEach(() => {
    ({ fixture, component, page } = createComponent(LoginComponent, LoginComponentPage));
    fixture.detectChanges();
  });

  it('should create component', () => {
    expect(component).toBeTruthy()
  });


});

问题原因分析
  1. 翻译加载器依赖缺失:引入的CommonUiMomentumChModule内置了CustomTranslationLoader,该加载器需要注入对应依赖,但测试模块未提供对应依赖,导致整个测试模块初始化失败,后续createComponent无法正常返回实例。
  2. 响应式表单依赖缺失:登录组件使用了响应式表单API,测试模块只导入了模板驱动表单的FormsModule,未导入ReactiveFormsModule,会导致FormBuilder注入失败。
  3. 核心路由依赖未Mock:登录组件构造函数依赖ActivatedRoute、Router,测试模块中未提供这两个依赖的Mock实现,注入失败。
  4. 依赖重复声明:AuthenticationService在providers中重复声明了两次,会导致依赖注入冲突。

修复方案

首先在测试文件顶部补充必要导入:

import { ReactiveFormsModule } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { TranslateLoader } from '@ngx-translate/core';
import { of } from 'rxjs';

修改测试模块的配置如下:

TestBed.configureTestingModule({
  declarations: [ LoginComponent ],
  providers: [
    // 覆盖翻译加载器,解决CustomTranslationLoader依赖问题
    {
      provide: TranslateLoader,
      useValue: {
        get: jest.fn(() => of({}))
      }
    },
    // Mock路由依赖
    {
      provide: Router,
      useValue: {
        navigateByUrl: jest.fn()
      }
    },
    {
      provide: ActivatedRoute,
      useValue: {
        snapshot: {
          queryParams: {}
        }
      }
    },
    // 保留自定义的AuthenticationService Mock,删除重复的provideMockObject声明
    {
      provide: AuthenticationService,
      useValue: MockAuthenticationService,
    },
  ],
  imports: [
    CommonUiMomentumChModule,
    CommonTestUtilsModule,
    FormsModule,
    ReactiveFormsModule // 新增响应式表单模块
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();

修改完成后重新运行测试即可:

  1. 翻译加载器的依赖错误消失,TestBed可以正常初始化
  2. 所有组件依赖都有对应Mock,createComponent可以正常返回fixture和component实例
  3. 基础断言expect(component).toBeTruthy()可以正常通过

内容的提问来源于stack exchange,提问作者pranami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 12:18:03