angular / angular/angular-cli

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

Abierto
#32,641 4 comentarios 30 reacciones 0 asignados Ver en GitHub
angular/build:unit-test area: @angular/build
Lenguaje dominante
TypeScript
Estrellas
27k
Forks
11.8k
Merge medio
14 h 23 min
PR fusionados (30 d)
162

Descripción

### 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.

Guía de contribución

Abrir la guía de contribución

Línea de trabajo

Comienza con el nuevo compilador de pruebas unitarias de Angular y su ruta de bundling de Vite; después, sigue dónde se rechazan las llamadas a vi.mock y cómo se transforman los archivos spec y setupFiles. Revisa los requisitos propuestos de detección en tiempo de compilación, hoisting, reescritura de imports y registro de mocks. Se considera terminado cuando se admitan las funciones vi enumeradas, preservando a la vez los live bindings de ESM, los sourcemaps, la cobertura y sin un cargador de módulos en tiempo de ejecución.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
angular, typescript, vite
Área
build-system, testing-qa
Tipo de issue
Nueva funcionalidad
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Estancado
Claridad
Necesita aclaración
Aptitud para principiantes
20/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.