如何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
相关产品推荐
相关产品推荐

