- pgmock é um servidor PostgreSQL simulado em memória para testes unitários e E2E, que roda em WebAssembly no Node.js e no navegador sem dependências externas
- Usuários de
node-postgres podem se conectar usando um objeto de configuração que não abre uma porta, e esse método também funciona no navegador
- No navegador, uma aplicação web não pode abrir portas TCP, mas é possível usar
PostgresMock.createSocket e a configuração do node-postgres; se o bundler analisar imports estáticos, podem aparecer avisos sobre módulos opcionais do Node.js
- A implementação atualmente executa um servidor PostgreSQL dentro de um emulador x86, priorizando evitar diferenças de comportamento entre testes e produção em vez de desempenho
- No longo prazo, quando o fork nativo de PostgreSQL em WASM amadurecer, o plano é oferecer as duas abordagens e depois mudar o padrão para WASM nativo
O que o pgmock oferece
- pgmock é um servidor PostgreSQL simulado em memória para testes unitários e E2E
- Não requer dependências externas e roda dentro de WebAssembly tanto no Node.js quanto no navegador
- A instalação pode ser feita via npm
npm install pgmock
Fluxo básico de uso
- O servidor em memória é criado com
PostgresMock.create(), e é possível receber uma string de conexão com listen(5432)
import { PostgresMock } from "pgmock";
const mock = await PostgresMock.create();
const connectionString = await mock.listen(5432);
- Ao usar
node-postgres, mock.getNodePostgresConfig() fornece um objeto de configuração que permite conectar sem escutar em uma porta
- Ao terminar, recomenda-se chamar
mock.destroy() para liberar os recursos
mock.destroy();
Suporte a navegadores e diferença em relação ao pglite
pgmock dá suporte completo ao ambiente de navegador
- Aplicações web não podem abrir portas TCP, mas podem usar
PostgresMock.createSocket e a configuração do node-postgres
- Se o bundler analisar imports estaticamente, podem aparecer avisos de que módulos opcionais do Node.js não existem; um exemplo de configuração do Webpack está em
examples/web-demo/next.config.mjs
- Se você quer apenas executar um banco de dados no navegador, pode considerar o pglite
- O pglite é mais rápido e mais leve, mas tem um conjunto de recursos limitado
pgmock foi projetado com o objetivo de oferecer a equivalência de recursos com o PostgreSQL de produção desejada em ambientes de teste
Como o PostgreSQL é executado em WebAssembly
- Há duas formas de executar PostgreSQL em WebAssembly
- A abordagem de fork nativo em WASM é mais rápida e usa muito menos memória, mas só oferece suporte ao modo de usuário único e não dá suporte a conexões nem extensões
pgmock atualmente usa a abordagem com emulador x86
- O objetivo é evitar inconsistências entre testes e produção
- Isso porque, em testes, desempenho geralmente não é um grande problema
- No médio prazo, quando o fork nativo de PostgreSQL em WASM amadurecer, o plano é oferecer ambas as opções
- Depois disso, o plano é mudar o padrão para WASM nativo, e a expectativa é que não haja muitos breaking changes grandes além da API interna
PostgresMock.subtle
Diferença em relação a projetos PostgreSQL existentes para navegador
pgmock oferece compatibilidade completa de recursos dentro do runtime JavaScript e não depende de um proxy de rede para comunicação
- Ele simula a stack de rede em JavaScript para se comportar como uma rede real, permitindo simular conexões TCP mesmo em plataformas que não permitem acesso a raw sockets
Extensibilidade e projetos relacionados
- Em teoria, outras imagens Docker ou bancos de dados também poderiam ser executados, mas isso não foi testado
- As seguintes implementações e projetos de base são mencionados
- v86: emulador x86
- Supabase & Snaplet: base da abordagem de executar PostgreSQL dentro de WebAssembly
- Stackframe: mencionada como a empresa que pagou salários durante o desenvolvimento do
pgmock
1 comentários
Comentários no Hacker News
Há alguns meses, venho criando no trabalho uma versão de Postgres em memória, com equivalência funcional ao banco de dados de produção
A vantagem é que não precisa de processo externo nem de proxy. Se a plataforma consegue executar WASM, também dá para rodar o pgmock no Node.js ou no navegador, e até criar um banco novo com dados mockados é tão simples quanto criar um objeto JavaScript
É um pouco diferente do pglite, que foi o que motivou a abertura do código do pgmock. O pgmock executa o Postgres original dentro de um emulador x86, enquanto o pglite compila diretamente um fork do Postgres para WASM nativo, ficando mais rápido e leve
Mas o pglite só oferece suporte a modo de usuário único e a algumas extensões, então não dá para conectar com um cliente Postgres comum, e isso é bem importante em testes E2E
Em teoria, talvez fosse possível adaptar isso para executar qualquer imagem Docker numa plataforma WebAssembly; fico curioso se há algum alvo específico que vocês gostariam de ver
Tenho algumas ideias de como adicionar um modo de múltiplas conexões, mas isso ainda deve levar um tempo. O PGlite também tem outras limitações relacionadas ao modo de usuário único; por exemplo, ainda não suporta
pg_notify, embora isso também esteja nos planos para ser corrigidoPor outro lado, este projeto é muito mais próximo do Postgres real, então há uma boa chance de simplesmente funcionar. Esses projetos de Postgres em memória parecem capazes de reduzir o tempo de execução dos testes para menos de um quarto, então vejo muito potencial na área de testes
Opinião de alguém que trabalha no PGlite
Recentemente quis rodar um pipeline de FFMPEG/SoX no cliente, mas havia dependências demais para recompilar facilmente com Emscripten. Fico curioso se essa abordagem também poderia ajudar em casos assim
Graças aos recursos relacionais, daria para adicionar e consultar junto toda a rica metadata específica de domínio que normalmente fica num banco relacional
Ao executar
select foo();, apareceError.captureStackTrace is not a function, no Firefox 124.0.2 no LinuxFico me perguntando por que não simplesmente executar os arquivos do Postgres em um ramdisk
Atualização: como isso pode ser executado em navegador/Node, imagino que o teste consiga criar, atualizar e excluir. Sou backend demais para entender bem a vantagem em relação a um ambiente normal de desenvolvimento. Seria legal explicar onde, quando e como isso é melhor
O motivo de fazer isso em WebAssembly é tornar o comportamento mais portátil entre plataformas, arquiteturas, navegador e até ambientes de edge, além de eliminar dependências externas a ponto de nem precisar de Docker
Como o emulador pode inicializar diretamente a partir de um estado já em execução, o boot do banco emulado fica mais rápido do que subir um banco real ou um contêiner Docker. Mas isso foi mais um efeito colateral feliz do que um objetivo de projeto
Fico pensando se não bastaria usar algo como https://testcontainers.com/. Não sei se ter um engine de contêiner como dependência externa é algo tão ruim assim
No momento em que você enfia mocks ali dentro, isso vira teste unitário. Continua útil, mas não é a mesma coisa. Um dos pontos centrais de E2E é saber que o teste está correto justamente porque não há mocks. Em vez de testar o Postgres, você está testando este sistema toda vez
Se você estiver criando um PG embarcado, leve e de baixo desempenho, faz sentido como teste de validação antes dos testes E2E reais, que são lentos. Eu também tenho esse tipo de uso
Fora isso, é um projeto legal e parece ser uma ferramenta útil quando você precisa de um shim de PG
No Postgres estamos usando savepoints, e mesmo em ramdisk isso não é tão rápido
Antigamente eu rodava todo tipo de servidor falso em memória nos testes. Hoje em dia, uso https://testcontainers.com para executar os alvos reais
Se um desenvolvedor de Prisma/Node.js só quiser um “Postgres enlatado” para desenvolvimento local, a versão recente em modo servidor do PGlite, pglite-server, pode ser uma opção melhor: https://github.com/kamilogorek/pglite-server
É mais rápido e pode manter os dados no sistema de arquivos, mas sob carga maior é menos estável do que um servidor de teste E2E baseado em emulador x86 completo. O pglite-server usava apenas 150MB de memória, enquanto o pgmock-server usava 830MB
Basta fazer checkout de um novo
.env.localcom dotenv e trocar oDATABASE_URLem todos os scripts de execução dopackage.jsondo nextjs/prismaDATABASE_URL="postgresql://postgres@localhost:5432/awesomeproject""db:pushlocal": "dotenv -e .env.local -- pnpm prisma db push"É muito fácil plugar isso em qualquer projeto, e dá para entender por que a Neon está patrocinando esse espaço
Não quero jogar água fria, mas eu não pretendo usar isso
Pode funcionar para aplicações simples, mas quando a complexidade aumenta — como risco de deadlock ou dependência do formato do banco de dados — até pequenas diferenças de comportamento podem virar problemas críticos, então o valor disso diminui
Hoje em dia prefiro ambientes de E2E com restrição de recursos. Isso dá ao executor de testes local a chance de quebrar quando alguém escreve um código absurdamente ineficiente
Além disso, a abordagem de criar um snapshot do banco de dados após alguns segundos e distribuir esse snapshot para as partições de teste é muito rápida, e muitas vezes já reduziu vários minutos da suíte de testes
É uma ideia interessante e deve ser uma boa experiência de aprendizado, mas acho que o público-alvo é limitado
O título é um pouco confuso. Se “foi feito na empresa”, então assumindo que foram usados recursos da empresa, parece que a propriedade intelectual desse projeto não pertence ao empregador?
Nesse caso, fico curioso se tecnicamente ele pode mesmo ser publicado como open source
Copyright 2024 Stackframe.. Parece que o autor trabalha na StackframeFico curioso sobre como isso se compara ao modo de compatibilidade com Postgres do H2
Bem legal. Se puder responder, tenho algumas curiosidades
O que levou a empresa a criar este projeto, se rodar o Postgres em um contêiner Docker era lento demais
Também queria saber como a configuração de CI para testes E2E mudou antes e depois de integrar o pgmock ao fluxo
Também tenho curiosidade se foi difícil migrar para essa solução
Se você fizer um dump dos dados de produção, remover todos os dados sensíveis e depois der truncate nas tabelas desnecessárias, como tabelas de log, terá uma boa cópia para desenvolvimento
Depois é só replicar isso para desenvolvimento, QA, E2E etc. O que o E2E precisa é exatamente dessas extensões, triggers, funções, views, índices e dados
Fico pensando se não daria para simplesmente usar Docker e ter um banco de testes separado
No Elixir, é assim que fazem, e o framework de testes envolve cada teste em uma transação e faz rollback para isolamento. Seria interessante entender qual é a vantagem dessa abordagem