You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何Mock Angular独立指令?测试环境配置方法咨询

解决Angular Standalone组件中Mock指令的问题

问题描述

在测试绑定了自定义指令(如consolePrint)的Angular Standalone组件时,创建了指令的Stub版本,但测试环境始终使用原指令,Stub无法生效;尝试通过注入器获取Stub时,提示该指令不存在于注入器中。

问题核心原因

由于TestDirectiveComponent是Standalone组件,它直接在自身的imports数组中导入了ConsolePrintDirective。这种情况下,Angular会优先使用组件导入的指令实例,而非TestBed.providers中配置的替换项——providers的替换规则仅适用于通过依赖注入获取的服务,对组件直接导入的指令/组件不生效。

解决方案

1. 调整测试模块的导入配置

在TestBed.configureTestingModule的imports中,移除原指令ConsolePrintDirective,替换为我们的Stub指令ConsolePrintDirectiveStub,保留组件TestDirectiveComponent的导入。

2. 修正测试逻辑

移除从根注入器获取Stub的代码,改为从元素的注入器中获取指令实例,确保拿到的是Stub版本。

修正后的完整测试代码

import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
import { ConsolePrintDirective, TestDirectiveComponent } from "./test-directive.component";
import { ActivatedRoute, Router } from "@angular/router";
import { of } from "rxjs";
import { By } from "@angular/platform-browser";
import { RouterTestingModule } from "@angular/router/testing";
import { Directive } from "@angular/core";

@Directive({
  selector: "[consolePrint]",
  standalone: true,
  host: { "(click)": "onClick()" },
})
export class ConsolePrintDirectiveStub {
  customText = "stub text";

  constructor() {}

  onClick() {
    console.log("onClick method called with text: ", this.customText);
  }
}
class DummyComponent {}

fdescribe("TestDirectiveComponent", () => {
  let component: TestDirectiveComponent;
  let fixture: ComponentFixture<TestDirectiveComponent>;
  let router: Router;

  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      imports: [
        TestDirectiveComponent,
        // 移除原指令,替换为Stub
        ConsolePrintDirectiveStub,
        RouterTestingModule.withRoutes([
          { path: "staff/100500", component: DummyComponent },
        ]),
      ],
      providers: [
        {
          provide: ActivatedRoute,
          useValue: {
            params: of({}),
            queryParams: of({}),
            data: of({}),
            url: of([]),
          },
        },
        {
          provide: Router,
          useValue: {
            createUrlTree: () => {},
            navigate: () => {},
            initialNavigation: () => {},
            events: of({}),
            serializeUrl: () => "",
            navigateByUrl: () => {},
          },
        },
        // 移除这里的providers替换,因为已经通过imports替换了指令
      ],
    }).compileComponents();
  }));

  beforeEach(async () => {
    router = TestBed.inject(Router);
    await router.initialNavigation();
    spyOn(router, "createUrlTree");

    fixture = TestBed.createComponent(TestDirectiveComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  fit("#consolePrint replace directive with stub", () => {
    const linkDes = fixture.debugElement.queryAll(By.css("a"));
    // 从元素的注入器中获取Stub指令
    const dir = linkDes[1].injector.get(ConsolePrintDirectiveStub);

    spyOn(dir, "onClick");

    linkDes[1].nativeElement.click();

    expect(dir.onClick).toHaveBeenCalled();
    expect(dir.customText).toBe("stub text");
  });
});

补充说明

如果你的组件不是Standalone的,而是通过NgModule声明的指令,那么可以通过declarations数组替换原指令为Stub,或者使用overrideDirective方法:

TestBed.configureTestingModule({
  declarations: [TestDirectiveComponent, ConsolePrintDirectiveStub],
})
.overrideDirective(TestDirectiveComponent, ConsolePrintDirective, {
  useClass: ConsolePrintDirectiveStub
});

但对于Standalone组件,最直接的方式就是替换imports中的指令为Stub。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:05:24