Allow using `vi.mock` (and related) functions in tests
- 主要语言
- TypeScript
- 星标
- 27k
- 派生
- 11.8k
- 平均合并
- 14 小时 23 分钟
- 30 天内合并 PR
- 162
描述
### 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()`
3. Hoist mock calls
4. Rewrite imports
#### Example Transform Injectable
**Before**:
```typescript
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**:
```typescript
// should initialize once in one environment
const __angularViMocks = (globalThis.__angular_vi_mocks ??= new Map());
__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](https://www.npmjs.com/package/ng-automocks-jest), it can stub any angular entities, both metadata and implementation.
贡献指南
调研方向
从新的 Angular 单元测试构建器及其 Vite 打包路径开始,然后跟踪 vi.mock 调用在哪些位置被拒绝,以及 spec 文件和 setupFiles 如何被转换。审查关于编译时检测、提升、导入重写和 mock 注册表的拟议要求。完成意味着支持列出的 vi 函数,同时保留 ESM 实时绑定、sourcemaps、coverage,并且不使用运行时模块加载器。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- angular, typescript, vite
- 领域
- build-system, testing-qa
- Issue 类型
- 功能
- 难度
- 5/5
- 预计耗时
- 一周以上
- 活跃度
- 停滞
- 描述清晰度
- 需要澄清
- 新手友好度
- 20/100