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

🪢 Recursos Compartilhados

Compartilhe estado, servidores, conexões de banco de dados e mais entre arquivos de teste paralelos, sem setup duplicado e sem conflitos.

Plugin
History
VersionChanges
v1.1.1
Corrigida a execução em isolation: none forçando o modo in-process e pulando o wiring de IPC.
v1.1.0
Adicionado write-back de mutações por referência via IPC para objetos aninhados e tipos especiais.
Introduzido o sistema ArgCodec com tags de símbolo, codecs embutidos e codecs customizados configuráveis.
v3.0.3-canary.68e71482
Migração de Recursos Compartilhados para um plugin dedicado.
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.

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.

Instalação

npm i -D @pokujs/shared-resources

Habilitando o Plugin

Adicione o plugin no arquivo de configuração do Poku:

// poku.config.ts
import { sharedResources } from '@pokujs/shared-resources';
import { defineConfig } from 'poku';

export default defineConfig({
plugins: [sharedResources()],
});

Você também pode registrar codecs customizados no setup do plugin:

// poku.config.ts
import { sharedResources } from '@pokujs/shared-resources';
import { defineConfig } from 'poku';

export default defineConfig({
plugins: [
sharedResources({
codecs: [
// valores ArgCodec customizados
],
}),
],
});

Uso Básico

1. Defina um Contexto de Recurso

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

// counter.ts
import { resource } from '@pokujs/shared-resources';

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 { resource } from '@pokujs/shared-resources';
import { assert, test } 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();
});

Em isolation: 'none', o plugin muda automaticamente para modo in-process. Nesse modo, os métodos continuam sendo expostos como funções assíncronas, mas as chamadas executam diretamente no mesmo processo (sem IPC).

Modos de Execução

O plugin escolhe o modo de execução com base nas configurações de isolamento do Poku:

  • isolation: 'process' (ou outro isolamento baseado em processo) usa modo IPC.
  • isolation: 'none' usa modo in-process e ignora o wiring de IPC.

Isso significa que Recursos Compartilhados funcionam de forma consistente nos dois modos sem configuração adicional.

Exemplo do Mundo Real

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

counter.ts

import { resource } from '@pokujs/shared-resources';

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

test-a.test.ts

import { resource } from '@pokujs/shared-resources';
import { assert, test } 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 { resource } from '@pokujs/shared-resources';
import { assert, test, 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 '@pokujs/shared-resources';

export const DatabaseContext = resource.create(
() =>
createConnection({
host: 'localhost',
user: 'root',
database: 'test',
}),
{
onDestroy: (connection) => connection.end(),
}
);
import type { RowDataPacket } from 'mysql2/promise';
import { resource } from '@pokujs/shared-resources';
import { assert, test } 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 por um pipeline de codecs.

Os codecs embutidos dão suporte a:

  • undefined
  • bigint
  • Date
  • Map
  • Set
  • Arrays e objetos (incluindo combinações aninhadas)

Instâncias de classe são serializadas como propriedades de dados próprias e enumeráveis por padrão. Quando os valores atravessam fronteiras de processo, cadeias de protótipo não são reconstruídas automaticamente.

Se você precisa de um comportamento de reconstrução customizado, registre um ArgCodec.

Codecs Customizados (ArgCodec)

Use codecs para tipos customizados que não podem ser representados fielmente pela semântica JSON padrão.

import type { ArgCodec } from '@pokujs/shared-resources';
import { resource } from '@pokujs/shared-resources';

class Token {
constructor(public value: string) {}
}

const tokenCodec: ArgCodec<Token> = {
tag: Symbol.for('example:Token'),
is: (v): v is Token => v instanceof Token,
encode: (v) => ({ value: v.value }),
decode: (v) => new Token((v as { value: string }).value),
};

resource.configure({
codecs: [tokenCodec],
});

resource.configure({ codecs }) é o local mais seguro para registrar codecs, porque módulos de recurso são avaliados tanto no processo pai quanto nos processos filhos.

sharedResources({ codecs }) configura codecs no processo pai. Use ambos apenas quando seu setup exigir.

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.