Allow using `vi.mock` (and related) functions in tests

未关闭
#32,641 4 条评论 30 个 reaction 已指派 0 人 在 GitHub 查看

已经有一个关联 PR 被合并了。

  • #32655 来自 @clydin —— 已合并

评估

难度
5/5
预计耗时
一周以上
新手友好度
20/100
Issue 类型
功能
描述清晰度
需要澄清
活跃度
停滞
技术栈
angular, typescript, vite

调研方向

从新的 Angular 单元测试构建器及其 Vite 打包路径开始,然后跟踪 vi.mock 调用在哪些位置被拒绝,以及 spec 文件和 setupFiles 如何被转换。审查关于编译时检测、提升、导入重写和 mock 注册表的拟议要求。完成意味着支持列出的 vi 函数,同时保留 ESM 实时绑定、sourcemaps、coverage,并且不使用运行时模块加载器。

由索引模型根据 Issue 内容生成。

描述

angular/build:unit-test area: @angular/build
Which @angular/* package(s) are relevant/related to the feature request?

core

Description

With the new Angular unit-test builder (Vite), test files are bundled into unified chunks.
Because of this full bundling step:

  • ESM modules are statically linked at build time
  • There is no runtime module graph available
  • vi.mock() cannot intercept module loading
  • Mocking entire modules (components/services/modules) is not possible

Currently if we use vi.mock function for automocking component/directives/services/pipes/etc..., we got an error:

Error: The "vi.mock" and related methods are not supported with the Angular unit-test system. Please use Angular TestBed for mocking.

TestBed overrides are insufficient because:

  • They replace Angular metadata (providers/imports), not module implementation
  • They cannot replace pure TS logic or side effects
  • They do not intercept ESM import bindings
Proposed solution

Instead of runtime interception (like Vitest normally does), Angular test builder should:

  1. Detect vi.mock() calls at compile time (in spec files, and in setupFiles)
  2. Hoist them
  3. Rewrite import bindings to use a generated mock registry
  4. Replace module resolution during bundling

I suggest to create esbuild plugin, that:

  1. Parse test file AST
  2. Detect:
  • vi.mock()
  • vi.unmock()
  • vi.doMock()
  • vi.doUnmock()
  • vi.importMock()
  • vi.importActual()
  • vi.hoisted()
  1. Hoist mock calls
  2. Rewrite imports
Example Transform Injectable

Before:

import { MyService } from './my-service';
import { MyOtherService } from './my-other-service';

vi.mock('./my-service', () => ({
  MyService:
    @Injectable({providedIn: 'root'})
    class {
      get() { return 'mock'; }
    }
}));

vi.mock('./my-other-service');

test(() => {
  const myService = new MyService();
  const myOtherService = new MyOtherService();
});

After:

// should initialize once in one environment
const __angularViMocks = (globalThis.__angular_vi_mocks ??= new Map<string, any>());

__angularViMocks.set(
  './my-service',
  (() => ({
    MyService: class {
      get() { return 'mock'; }
      static ɵprov = {
         providedIn: 'root',
         factory: () => new this();
      };
      static ɵfac = () => new this(); 
    }
  }))()
);

__angularViMocks.set(
  './my-other-service',
  (() => ({
    MyOtherService: class {
      get = vi.fn(); // maybe better to put `vi.fn()` to MyOtherService.prototype.get = vi.fn();
      static ɵprov = {
         providedIn: 'root',
         factory: () => new this();
      };
      static ɵfac = () => new this(); 
    }
  }))()
);

import * as __angularViActualMod1 from './my-service';
import * as __angularViActualMod2 from './my-other-service';

// We should replace that import in every dependent chunk.
const { MyService } = __angularViMocks.get('./my-service') ?? __angularViActualMod1;
const { MyOtherService } = __angularViMocks.get('./my-other-service') ?? __angularViActualMod2;
Requirements:
  • Must mock full implementation (not only metadata) for any modules, as vi.mock() does
  • Must stub components, services, modules, pipes, directives both metadata and implementation
  • Must preserve ESM live bindings semantics
  • Must preserve sourcemaps and coverage
  • Must not require runtime module loader
Alternatives considered

Currently we have no choice, but stay with slow and inefficient jest + jest-preset-angular.

I've implemented deep-automocking infrastructure for jest.mock() ng-automocks-jest, it can stub any angular entities, both metadata and implementation.

主要语言
TypeScript
星标
27k
派生
11.8k
平均合并
16 小时 21 分钟
30 天内合并 PR
170

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

angular/angular-cli 的其他 Issue

查看 angular/angular-cli 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。