Pular para o conteúdo principal
Versão: v4.x.x (Canary)

🪢 Recursos Compartilhados

History
VersionChanges
v3.0.3-canary.5b54775d
API refatorizada com limpeza automática, lazy loading e inferência de tipos.
v3.0.3-canary.ffab4562
Serialização melhorada e suporte a Windows.
v3.0.3-canary.60ff5ce2
Introdução de Recursos Compartilhados.
1Experimental

Compartilhe estado e métodos entre arquivos de teste e processos, habilitando padrões avançados de testes de integração e end-to-end.

O que são Recursos Compartilhados?

Recursos Compartilhados permitem que você defina um recurso com estado uma vez e o acesse a partir de múltiplos arquivos de teste e processos. Cada teste obtém a mesma instância de recurso, permitindo que você:

  • Compartilhe conexões de banco de dados, armazenamentos em memória ou clientes de API entre testes.
  • Coordene estado e asserções entre testes paralelos ou sequenciais.
  • Gerencie a lógica de setup e teardown em um único lugar.

O sistema de recursos lida automaticamente com comunicação entre processos, serialização e limpeza — para que você possa focar em escrever testes.

Habilitando Recursos Compartilhados

Para usar Recursos Compartilhados, passe a flag --sharedResources ao executar o Poku:

poku --sharedResources

Ou em um arquivo de configuração:

{
"sharedResources": true
}

Uso Básico

1. Defina um Contexto de Recurso

Crie um arquivo de recurso usando resource.create():

// counter.ts
import { resource } from 'poku';

export const CounterContext = resource.create(() => ({
count: 0,
increment() {
this.count++;
return this.count;
},
getCount() {
return this.count;
},
}));

resource.create() detecta automaticamente o caminho do arquivo do seu módulo de recurso. Ele recebe uma função factory como primeiro argumento — uma função que retorna o objeto de recurso com métodos.

Solução de problemas: Se o resource.create() não conseguir detectar o caminho do módulo automaticamente, você pode definir module explicitamente como fallback:

export const CounterContext = resource.create(
() => ({
/* ... */
}),
{ module: __filename } // ou import.meta.url para ESM
);

2. Acesse o Recurso nos Testes

Use resource.use() para obter o recurso:

import { test, assert, resource } from 'poku';
import { CounterContext } from './counter';

test('incrementar contador', async () => {
const counter = await resource.use(CounterContext);

const current = await counter.getCount();
assert.equal(current, 0);

await counter.increment();
assert.equal(await counter.getCount(), 1);
});

3. Métodos são Chamadas de Procedimento Remoto

Todos os métodos no seu recurso se tornam RPCs assíncronos que funcionam entre processos. O estado é sincronizado automaticamente:

test('estado compartilhado entre testes', async () => {
const counter = await resource.use(CounterContext);

// Isto obtém o estado atual do recurso
const value = await counter.getCount();
assert.equal(value, 1); // reflete mudanças de outros testes

// Isto muta o estado compartilhado
await counter.increment();
});

Exemplo do Mundo Real

Aqui está um exemplo completo de dois testes compartilhando um contador:

counter.ts

import { resource } from 'poku';

export const CounterContext = resource.create(() => ({
count: 0,
increment() {
this.count++;
return this.count;
},
getCount() {
return this.count;
},
}));

test-a.test.ts

import { assert, test, resource } from 'poku';
import { CounterContext } from './counter';

test('Test A: Incrementar Contador', async () => {
const counter = await resource.use(CounterContext);

const current = await counter.getCount();
assert.strictEqual(current, 0, 'Deve iniciar em 0');

await counter.increment();
assert.strictEqual(await counter.getCount(), 1, 'Deve ser 1 após incremento');
});

test-b.test.ts

import { assert, test, resource, waitForExpectedResult } from 'poku';
import { CounterContext } from './counter';

test('Test B: Verificar Estado do Contador', async () => {
const counter = await resource.use(CounterContext);

// Se você precisar que o resultado do Test A esteja pronto antes de prosseguir,
// é recomendado aguardar o estado esperado para evitar instabilidade.
await waitForExpectedResult(counter.getCount, 1);

await counter.increment();
assert.strictEqual(await counter.getCount(), 2, 'Deve ser 2 após incremento');
});

Quando estes testes são executados, eles compartilham a mesma instância do contador. O Test B vê as mudanças de estado feitas pelo Test A.

Uso Avançado

Ciclo de Vida do Recurso

Para executar uma ação quando o recurso é destruído, use a opção onDestroy:

import { createConnection } from 'mysql2/promise';
import { resource } from 'poku';

export const DatabaseContext = resource.create(
() =>
createConnection({
host: 'localhost',
user: 'root',
database: 'test',
}),
{
onDestroy: (connection) => connection.end(),
}
);
import type { RowDataPacket } from 'mysql2/promise';
import { test, assert, resource } from 'poku';
import { DatabaseContext } from './db.js';

type QueryResult = RowDataPacket & {
total: number;
};

await test('Connection', async () => {
const connection = await resource.use(DatabaseContext);
const [rows] = await connection.execute<QueryResult[]>(
'SELECT 1 + 1 AS total'
);

assert.strictEqual(rows[0].total, 2);
});
dica

Recursos podem ser inicializados de forma assíncrona.

Notas Importantes

Serialização

Argumentos e valores de retorno são serializados usando JSON quando comunicados entre processos. Referências circulares e valores não serializáveis (como sockets, streams e Buffers) não são suportados. Instâncias de classes são reduzidas às suas propriedades enumeráveis, perdendo sua cadeia de protótipos. Mantenha seus métodos de recurso simples com tipos básicos: strings, números, booleanos, arrays e objetos simples.

Carregamento Preguiçoso

Recursos são criados sob demanda na primeira vez que você chama resource.use(). Se um teste nunca acessa um recurso, ele nunca é criado.