Angular Jest单元测试mock HTMLElement报自定义元素注册表错误
报错根因
两个报错本质都是Jest默认依赖的JSDOM环境的固有规则限制:
HTMLImageElement、HTMLElement、Element这类Web IDL定义的DOM抽象接口,不允许直接通过new关键字手动实例化。浏览器和JSDOM都只会在解析DOM、内部生成节点的时候调用这些构造器,用户手动调用就会抛出Illegal constructor或者你看到的"构造器不在自定义元素注册表内"的错误。- 就算你通过类型断言绕开TS检查,手动构造的对象也不满足你指令里的判断逻辑:你的
onClick方法是拿target.constructor和HTMLImageElement做全等匹配,手动拼的对象、或者通过其他方式生成的对象原型链不对,constructor匹配不上,逻辑根本走不到图片处理分支。 - 另外你原测试代码直接传
null作为BsModalService的依赖也有问题,后续调用openLightbox的时候会触发空指针错误,同时你没有给openLightbox方法加监视桩,toHaveBeenCalled断言也无法正常执行。
正确测试实现
创建DOM元素的标准方式是调用document.createElementAPI,这个方法在JSDOM中完全支持,生成的元素原型链和真实浏览器完全一致,constructor会正确对应到对应的DOM构造器,能完美匹配你指令里的判断逻辑。
完整可运行的测试代码如下:
import { HTMLClickDirective } from "./html-click.directive"; import { BsModalService } from "ngx-bootstrap/modal"; describe('HTMLClickDirective', () => { let mockModalService: jest.Mocked<BsModalService>; let directive: HTMLClickDirective; beforeEach(() => { // 初始化mock的modal服务 mockModalService = { show: jest.fn().mockReturnValue({ hide: jest.fn() }) } as unknown as jest.Mocked<BsModalService>; directive = new HTMLClickDirective(mockModalService); }); it('点击图片节点时应该触发灯箱打开逻辑', () => { // 正确创建img节点,不要用new HTMLImageElement const testImg = document.createElement('img'); testImg.setAttribute('src', 'test-image-path.png'); // 给openLightbox方法加监视桩 const openLightboxSpy = jest.spyOn(directive, 'openLightbox'); // 构造mock点击事件,补全指令里会调用的preventDefault方法 const mockClick = { target: testImg, preventDefault: jest.fn() } as unknown as MouseEvent; directive.onClick(mockClick); // 断言逻辑符合预期 expect(mockClick.preventDefault).toHaveBeenCalled(); expect(openLightboxSpy).toHaveBeenCalledWith('test-image-path.png'); expect(mockModalService.show).toHaveBeenCalled(); }); it('点击STRONG节点时应该触发对应锚点滚动', () => { const testStrong = document.createElement('strong'); testStrong.innerText = 'intro'; // 提前插入对应id的锚点节点 const anchorEl = document.createElement('div'); anchorEl.id = 'intro'; anchorEl.scrollIntoView = jest.fn(); document.body.append(anchorEl); const mockClick = { target: testStrong, preventDefault: jest.fn() } as unknown as MouseEvent; directive.onClick(mockClick); expect(mockClick.preventDefault).toHaveBeenCalled(); expect(anchorEl.scrollIntoView).toHaveBeenCalled(); }); });
注意:不要依赖TypeScript类型断言伪造DOM节点,你的指令逻辑是基于运行时的原型链constructor做判断,只有通过
document.createElement生成的真实DOM节点才能满足判断条件,字面量对象、手动new构造器生成的对象即使TS类型校验通过,运行时逻辑也会不符合预期。
内容的提问来源于stack exchange,提问作者jugglervr
相关产品推荐
相关产品推荐

