Testando componentes Vue no navegador
Olá! Um dos meus projetos de longo prazo aqui é descobrir como escrever Javascript frontend sem usar Node ou qualquer outro tempo de execução JS de servidor.
Um problema que encontro muito em meus projetos front-end JS é que não sei como escrever testes para eles. Eu tentei usar o Playwright no passado, mas parecia lento e complicado iniciar esses novos processos do navegador o tempo todo, e isso envolvia algum código Node para orquestrar os testes.
O resultado é que eu simplesmente não testo meu código de front-end, o que não parece ótimo. Normalmente eu também não atualizo muito meus projetos então não aparece muito, mas seria bom poder fazer alterações com mais confiança! Portanto, uma maneira de fazer testes de front-end que gosto está na minha lista de desejos há muito tempo.
ideia: basta executar os testes na aba do navegador
Alex Chan escreveu um ótimo post há algum tempo chamado Testando JavaScript sem uma estrutura (de terceiros) em resposta a um dos meus posts anteriores nesta série que explicava como escrever uma pequena estrutura de teste unitário que roda em uma página no navegador.
Adorei esse post na época, mas ele falava apenas sobre testes unitários e eu queria escrever testes de integração ponta a ponta para meus componentes Vue, mas não sabia como fazer isso.
Então, outro dia, quando eu estava conversando com Marco e ele disse algo como “sabe, você pode simplesmente executar testes para seus componentes Vue no navegador”, pensei “ei, deveria tentar de novo!!!”
Acabei de fazer tudo isso ontem, então certamente há muito o que melhorar, mas gostaria de anotar algumas coisas que percebi sobre o processo antes que me esqueça.
Isso foi um pouco complicado para mim porque o site Vue geralmente assume que você está usando o Node como parte do seu processo de construção de alguma forma (há muitas etapas da “etapa 1:
npm install THING), e eu não queria usar Node/Deno/etc. Mas acabou não sendo muito complicado.
O projeto sobre o qual vou falar sobre testes é este site de feedback de zine que escrevi em 2023.
a estrutura de teste: QUnit
Eu usei o QUnit. Funcionou muito bem, mas não tenho nada de interessante a dizer sobre como funciona, então vou deixar por isso mesmo. Acho que a abordagem de Alex de “escrever sua própria estrutura de teste” também teria funcionado. Eu segui essas instruções.
Gostei do fato de o QUnit ter um botão “reexecutar teste” que executará novamente apenas 1 teste. Como há tantas solicitações de rede em meus testes, ter uma maneira de executar apenas um teste torna muito menos confuso depurar o teste.
etapa 1: configurar o componente para teste
A primeira coisa que precisei fazer foi configurar meus componentes Vue no ambiente de teste.
Mudei meu aplicativo principal para colocar todos os meus componentes window._componentsmais ou menos assim:
const components = {
'Feedback': FeedbackComponent,
...
}
window._components = components;
Então consegui escrever um mountComponent função que faz basicamente exatamente a mesma coisa que meu aplicativo principal normal faz (renderizar um pequeno modelo com o componente que desejo usar). As únicas diferenças são:
- Opcionalmente, posso passar alguns dados extras para usar como acessórios.
- Ele monta o componente em um div invisível temporário que será removido do DOM após a conclusão do teste. A div está posicionada fora da página (
position: absolute; top: -10000, ...) então você não pode vê-lo.
Aqui está o que usando o mountComponent função se parece com:
const {div} = mountComponent(
' ',
{feedbacks: (testFeedback)},
);
e aqui está o código para isso:
function mountComponent(template, data) {
const app = Vue.createApp({
template: template,
data: () => data,
})
for (const (c, v) of Object.entries(window._components)) {
app.component(c, v);
}
const div = document.getElementById('qunit-fixture')
.appendChild(document.createElement('div'));
return div;
}
O resultado é uma div onde posso clicar programaticamente, preencher os dados do formulário, verificar se o conteúdo correto aparece, etc.
passo 2: adicione alguns dados do fixture
Como eu estava escrevendo testes de integração ponta a ponta para garantir que meu cliente JS funcionasse corretamente com meu servidor, precisei ter alguns dados de teste em meu banco de dados. Então, escrevi cerca de 25 linhas de SQL para configurar alguns dados de teste em meu banco de dados e adicionei um endpoint ao meu servidor de desenvolvimento para executar o SQL e redefinir os dados de teste para um estado conhecido.
async function reset() {
return fetch('/api/reset_test_data', {method: "POST"})
}
Então eu apenas corro await reset() no início de qualquer teste que precise dos dados de teste.
Meu reset() na verdade, a função nem sempre redefine totalmente tudo o que é ruim, mas era viável no início e sempre pode ser melhorado.
etapa 3: um teste básico
Esta é a aparência de um teste básico! Basicamente, estamos renderizando o div e nos certificando de que ele contém alguns dados aproximadamente corretos.
QUnit.test('renders feedback content', async function (assert) {
const {div} = mountComponent(
' ',
{feedbacks: (testFeedback)},
);
assert.ok(div.textContent.includes('loved this section'));
})
Essas são todas as peças básicas! Agora, aqui estão alguns problemas que encontrei ao longo do caminho
esperando que partes da página sejam renderizadas
Tenho muitas solicitações de rede em meus testes e leva tempo para que elas terminem e para que o código Vue faça o que tem a ver com os resultados e atualize o DOM.
Acho que todos nós aprendemos há muito tempo que colocar aleatoriamente sleep() chamadas em seus testes e esperar que os tempos estejam corretos é lento, instável e extremamente frustrante, então eu precisava de uma maneira diferente.
Pelo que eu sei, a maneira normal de lidar com isso é descobrir uma maneira de saber no DOM se está tudo bem em prosseguir ou não. Tipo “se este botão estiver visível, podemos”.
Então eu escrevi um pouco waitFor() função que pesquisa a cada 20 ms para ver se uma condição já foi concluída. O tempo expira após 2 segundos.
Veja como é usá-lo:
QUnit.test("click item", async function (assert) {
const {div} = mountComponent(
' ',
{});
const item = await waitFor(() => div.querySelector('.feedback-item'));
item.click();
// rest of test goes here...
})
Parece que existem muitas implementações desse conceito por aí e todas são mais bem pensadas do que as minhas. (de um rápido Google: qunit-wait-for, dramaturgo expect.poll)
descobrir a coisa certa a esperar não é simples
Em alguns casos eu pensamento Eu identifiquei a coisa certa a esperar no DOM (“basta esperar que esta área de texto apareça!”), mas descobri que, devido a alguns detalhes internos de como meu programa funciona, na verdade eu precisava esperar por algo mais tarde, o que era difícil de definir.
Acabei alterando um dos meus componentes para adicionar algum valor aleatório ao DOM quando ele finalizava uma ação importante (como data-this-thing-is-ready=true) que não foi ótimo.
Meu melhor palpite é que a maneira correta de corrigir esse tipo de problema de teste é uma refatoração que também torna o aplicativo mais confiável para os usuários: se houver um elemento no DOM que não esteja realmente pronto para o usuário interagir, talvez eu não devesse exibi-lo ainda!
adicionando algumas classes CSS para identificar coisas (mas está certo?)
Acabei adicionando algumas classes aos elementos HTML que precisava encontrar nos testes, seja porque precisava clicar neles ou esperar que aparecessem no DOM.
Talvez eu queira mudar essa abordagem mais tarde – as estruturas de teste de front-end parecem sugerir evitar o uso de classes CSS e, em vez disso, usar algo como getByRole ou, como último recurso, algo como data-testid. Parece que há uma maneira de tornar o aplicativo mais acessível e mais fácil de testar ao mesmo tempo.
preencher formulários é complicado
Para preencher um formulário, não posso simplesmente definir o valuetambém preciso despachar um evento para informar ao Vue que o elemento foi alterado. Por exemplo, checkbox e
textarea precisam de diferentes tipos de eventos.
textarea.value="banana banana banana";
textarea.dispatchEvent(new Event('input'));
checkbox.checked = true;
checkbox.dispatchEvent(new Event('change'));
Isso é meio chato e me fez perceber por que eu poderia querer usar algum tipo de biblioteca de testes de UI, por exemplo:
cobertura de teste
Quero ter uma ideia de qual foi a minha cobertura de teste e descobri que o Chrome, na verdade, tem um recurso integrado de cobertura de código para JS e CSS!
Meu JS está agrupado em um arquivo chamado bundle.js com esbuild, então eu poderia apenas olhar bundle.js e veja quais linhas não foram cobertas.
O processo foi um pouco complicado: tive que desligar os mapas de origem nas ferramentas de desenvolvimento do Chrome para que isso funcionasse, e há uma série específica e não muito óbvia de ações que preciso realizar para ver os dados de cobertura.
isso foi tão divertido!
Como de costume nesses posts, nunca trabalhei como desenvolvedor frontend ou backend (além de mim mesmo!) e sinto que estou constantemente aprendendo como realizar tarefas superbásicas.
Eu realmente me diverti muito fazendo isso. Meus projetos de front-end sempre parecem tão frágeis porque não foram testados, e talvez um dia eu tenha um conjunto de testes no qual tenha confiança!
Algumas coisas que ainda estou pensando:
- Enquanto escrevia este post, encontrei uma biblioteca de testes de frontend chamada Testing Library, que contém muitas diretrizes sobre como escrever testes que são muito diferentes das minhas ideias iniciais. Experimentei reescrever tudo para usar a Biblioteca de Testes e me senti muito bem, então veremos no que dá. Eles distribuem um
.umd.jsarquivo que funciona sem o Node. - Não tenho certeza de como me sinto por não ter como executar esses testes na linha de comando. Talvez haja uma maneira simples de trabalhar principalmente no navegador, mas também há uma maneira de executá-los no CI, se eu quiser?



