🪢 Recursos Compartilhados
History
| Version | Changes |
|---|---|
| v3.0.3-canary.5b54775d | |
| v3.0.3-canary.ffab4562 | |
| v3.0.3-canary.60ff5ce2 |
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 definirmoduleexplicitamente 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);
});
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.