---
title: "Fake timers do Vitest no Angular: o modelo mental depois do fakeAsync"
description: "Como pensar nos fake timers do Vitest no Angular depois do fakeAsync: os dois trabalhos que o fakeAsync fundia, o mapa de API, as partes específicas do Angular (effects, change detection, RxJS), e quando não usar fake timers."
deck: "O `fakeAsync` fazia dois trabalhos ao mesmo tempo: avançava o tempo virtual e drenava os microtasks de Promise. [O Vitest separa os dois](https://angular.dev/guide/testing/migrating-to-vitest): o `vi.useFakeTimers()` controla o tempo, e você dá `await` nos microtasks. Quase todo teste de tempo no Angular sob Vitest é essa única ideia, aplicada."
author: "André Ramos"
url: "https://andreramos.dev/pt/angular/vitest-fake-timers-vs-fakeasync-angular/"
lang: "pt-BR"
type: "article"
---

# Fake timers do Vitest no Angular: o modelo mental depois do fakeAsync

> O `fakeAsync` fazia dois trabalhos ao mesmo tempo: avançava o tempo virtual e drenava os microtasks de Promise. [O Vitest separa os dois](https://angular.dev/guide/testing/migrating-to-vitest): o `vi.useFakeTimers()` controla o tempo, e você dá `await` nos microtasks. Quase todo teste de tempo no Angular sob Vitest é essa única ideia, aplicada.

## O fakeAsync fazia dois trabalhos

O `fakeAsync` fundia duas responsabilidades num helper só. O `tick(ms)` adiantava timers, e também drenava callbacks de Promise pendentes. Por causa dessa fusão, um teste que esperava um `.then()` dentro de um `setTimeout` simplesmente funcionava; e é ela que faz a migração pro Vitest parecer um passo atrás no começo: o Vitest não junta os dois.

O Vitest te dá as duas metades separadas. O tempo virtual é `vi.useFakeTimers()` mais `vi.advanceTimersByTime(ms)`. A metade dos microtasks é o seu próprio `await`. Guarda essa separação na cabeça e o resto da migração é mecânico: onde o `fakeAsync` fazia os dois de graça, agora você faz em dois passos.

*O mesmo teste, antes e depois*

```ts
// O fakeAsync fazia os dois trabalhos numa chamada:
it('faz polling três vezes', fakeAsync(() => {
  const service = TestBed.inject(PollService);
  service.start();
  tick(3000);
  expect(service.count()).toBe(3);
}));

// Vitest: o tempo é explícito, e não há Promise aqui, então sem await.
it('faz polling três vezes', () => {
  vi.useFakeTimers();
  const service = TestBed.inject(PollService);
  service.start();
  vi.advanceTimersByTime(3000);
  expect(service.count()).toBe(3);
});
```

## O mapa de API

A maior parte da conversão é consulta de tabela. O único ponto que exige julgamento é se há uma Promise na cadeia, o que decide entre o avanço síncrono e o assíncrono.

| O que o fakeAsync fazia | Equivalente no Vitest | Quando |
|---|---|---|
| `tick(ms)` | `vi.advanceTimersByTime(ms)` | Só timers, nada retorna Promise |
| `tick(ms)` com Promise na cadeia | `await vi.advanceTimersByTimeAsync(ms)` | Async validator, `.then`, `whenStable` |
| `flush()` | `vi.runAllTimers()` / `await vi.runAllTimersAsync()` | Drenar todos os timers pendentes de uma vez |
| `discardPeriodicTasks()` | `vi.clearAllTimers()` | Parar intervals antes de assertar |
| reset entre testes | `vi.useRealTimers()` no `afterEach` | Sempre |

## A regra única que pega todo mundo

O `vi.advanceTimersByTime()` dispara timers. Ele não drena microtasks de Promise. Então no instante em que uma Promise entra na cadeia (um async validator, um `.then`, um `whenStable`), o avanço síncrono dispara o timer mas sua assertion roda antes do callback resolver. O control ainda está `PENDING`, o valor ainda é `undefined`, e a falha parece impossível.

A correção é uma substituição: `await vi.advanceTimersByTimeAsync(ms)`, que avança o tempo e drena os microtasks. A regra de bolso é pequena o suficiente pra decorar: qualquer Promise na cadeia, use a variante `...Async` e dê `await`. Essa regra única é a maior parte do que faz os testes de tempo do Vitest parecerem diferentes do `fakeAsync`.

*Promise na cadeia precisa do avanço assíncrono*

```ts
it('marca username em uso', async () => {
  vi.useFakeTimers();
  const control = new FormControl('taken', {
    asyncValidators: [uniqueUsernameValidator(['taken'])],
  });

  await vi.advanceTimersByTimeAsync(200);   // tempo E microtasks

  expect(control.status).toBe('INVALID');
});
```

## As partes que não são timers

Três coisas do Angular parecem tempo mas não são, então apelar pro `advanceTimersByTime` não faz nada de útil.

Os schedulers do RxJS de fato pegam carona no timer. `debounceTime`, `throttleTime` e o async scheduler rodam no mesmo `setInterval` que o `vi` controla, então avançar o tempo dispara eles sem tratamento especial. Essa parte é fácil.

Effects de signal não. Um effect é processado durante o change detection, não num relógio, então avançar o tempo nunca o roda. Dirija com `TestBed.tick()` (`TestBed.flushEffects()` está deprecado). E o change detection ainda precisa rodar: avançar o tempo pode atualizar um signal, mas um componente OnPush só renderiza depois de `detectChanges()`.

*Um effect é processado pelo change detection, não por timer*

```ts
const service = TestBed.inject(ActivityLogService);

TestBed.tick();                 // processa o effect, sem timer envolvido
service.status.set('busy');
TestBed.tick();
expect(service.history()).toEqual(['idle', 'busy']);
```

## Quando não apelar pros fake timers

Fake timers nem sempre são a ferramenta certa. Para um stream com muito tempo, um teste marble modela a linha do tempo direto e nunca toca em Zone.js ou fake timers: `cold('a-b-c-------|')` por `debounceTime(3)` assere `'-------c----|'` no `TestScheduler`. Para várias emissões ao longo do tempo, isso costuma ser mais claro que avançar um relógio na mão.

E se nada agenda um timer, você não precisa de fake timers. Um teste de Promise pura só precisa ser `async` e dar `await` no valor. Fake timers merecem espaço só quando há tempo virtual a controlar.

Esse é o modelo inteiro: controle o tempo com `vi.useFakeTimers()`, drene microtasks com `await`, processe effects com `TestBed.tick()`, e deixe os testes marble cuidarem dos streams com muito tempo. O [erro ProxyZone](/pt/angular/angular-vitest-fakeasync-proxyzone-error) e suas reescritas são esse modelo aplicado a um teste quebrado; [se vale migrar uma suíte](/pt/angular/move-angular-test-suite-to-vitest) é a decisão acima dele.

## Artefato reaproveitável: Qual ferramenta de tempo para qual teste

- Só timers, nada retorna Promise: `vi.advanceTimersByTime(ms)`.
- Promise em qualquer ponto da cadeia (async validator, `.then`, `whenStable`): `await vi.advanceTimersByTimeAsync(ms)`.
- Um effect de signal: `TestBed.tick()`, não um timer.
- Um componente OnPush: chame `detectChanges()` depois de avançar o tempo, para ele renderizar.
- Um stream RxJS com muito tempo: um teste marble no `TestScheduler`, sem fake timers.
- Uma Promise pura sem timer: faça o teste `async` e dê `await`; pule fake timers.
- Sempre: `vi.useRealTimers()` no `afterEach`.

## Perguntas frequentes

### Qual a diferença entre vi.advanceTimersByTime e vi.advanceTimersByTimeAsync?

O síncrono `vi.advanceTimersByTime(ms)` dispara só timers. O assíncrono `vi.advanceTimersByTimeAsync(ms)` também drena os microtasks de Promise entre eles. Use a variante assíncrona sempre que houver uma Promise na cadeia (um async validator, um `.then`, um `whenStable`), ou sua assertion roda antes do resultado ser aplicado.

### Por que o tick() do fakeAsync resolvia timers e Promises?

Porque o `fakeAsync` fundia dois trabalhos: avançar o tempo virtual e drenar os microtasks pendentes. O `tick(ms)` fazia os dois. O Vitest separa, então sob o novo runner você avança o tempo e drena os microtasks em dois passos, o segundo sendo um `await`.

### Como processo um effect de signal com fake timers do Vitest?

Você não usa timers para effects. Um effect de signal é processado durante o change detection, não num relógio, então avançar o tempo não faz nada por ele. Chame `TestBed.tick()` (o `TestBed.flushEffects()` existe mas está deprecado).

### Preciso de fake timers para um teste marble do RxJS?

Não. O `TestScheduler` modela a linha do tempo sozinho e não depende de Zone.js nem de fake timers, então testes marble seguem funcionando sem mudança. Para streams com várias emissões ao longo do tempo, costumam ser mais claros que avançar um relógio na mão.

### Quando devo evitar fake timers por completo?

Quando nada agenda um timer. Se um teste só espera uma Promise sem `setTimeout` ou interval por baixo, faça-o `async` e dê `await` no valor. Fake timers compensam só quando há tempo virtual a controlar.

## Fontes consultadas

- https://angular.dev/guide/testing
- https://angular.dev/guide/testing/migrating-to-vitest
- https://angular.dev/api/core/testing/TestBed
- https://vitest.dev/api/vi
- https://vitest.dev/guide/mocking
- https://blog.angular.dev/announcing-angular-v21-57946c34f14b

## Leia também

- [fakeAsync e o erro ProxyZone: consertando testes Angular no Vitest](https://andreramos.dev/pt/angular/angular-vitest-fakeasync-proxyzone-error/)
- [Quando migrar uma suíte Angular para Vitest](https://andreramos.dev/pt/angular/move-angular-test-suite-to-vitest/)
- [O código que quebra quando Angular fica zoneless](https://andreramos.dev/pt/angular/code-that-breaks-when-angular-goes-zoneless/)
