Introdução
A medida que evoluímos como desenvolvedores eventualmente chegará o momento de lidar com testes automatizados, é algo inevitável e que pode parecer bem intimidador a primeiro momento. Neste tutorial meu objetivo é te mostrar como você pode começar a testar seus componentes desde a configuração do projeto até a implementação de um componente de pesquisa.
Ferramentas Escolhidas
Neste tutorial utilizarei as libs Vue Test Utils e Jest para implementar os testes de unidade. Explicarei brevemente pra que serve cada lib antes de partirmos para o código:
Vue Test Utils
Este é o utilitário de testes oficial do Vuejs. Ele nos permite montar um componente em memória como se estivesse sendo renderizado no browser e a partir daí interagir com ele.
Jest
O Jest é um framework de testes para a linguagem Javascript desenvolvido pelo Facebook. Por ser escrito em Javascript ele funciona bem tanto no back-end com Nodejs quanto no front-end com frameworks como Vuejs, Angular e React. É o Jest que nos permite de fato testar a aplicação.
Criando o Projeto
Utilize o vue-cli
para criar o projeto executando o comando abaixo:
vue create vue-tests
Selecione a primeira opção Default ([Vue 2, babel, eslint])
e pressione Enter.
Obs.: Caso queira, pode selecionar a opção Manually select features e então marcar a opção Unit Testing para adicionar automaticamente as ferramentas de teste ao seu projeto
Configurando Ferramentas de Teste
Se você adicionou o Jest na etapa anterior pode ignorar esta etapa do tutorial.
Seguindo as instruções de instalação da documentação só precisamos executar os comandos abaixo para instalar as ferramentas:
vue add unit-jest
npm install --save-dev @vue/test-utils
Agora precisamos criar um script dentro do arquivo package.json
para manter o Jest executando e observando os testes unitários, para adicione o script abaixo no seu package.json:
"scripts": { ... "test:watch": "jest --verbose --watch" }
Obs.: Caso ocorra erro EMFILE: too many open files ao executar os testes será necessário instalar o utilitário Watchman. Para fazer a instalação siga as instruções da documentação oficial.
Implementação inicial do projeto
Antes de começar a implementar os testes vamos criar o layout inicial do componente, assim garantimos que o visual do componente estará de acordo com o que planejamos. Este será um projeto bem simples onde implementaremos um input de pesquisa. Crie um novo diretório dentro de /components
chamado search-input e dentro dele um arquivo index.vue com o código abaixo:
<template> <div class="input"> <input type="text" placeholder="Pesquisar..." /> <span class="input__clear">×</span> </div> </template> <script> export default { } </script> <style scoped> .input { display: flex; width: 100%; position: relative; } .input input { width: 100%; padding: 8px 22px 8px 10px; font-size: 1rem; border: 1px solid #e3e3e3; background: #fafafa; border-radius: 6px; outline: none; } .input input:focus { border-color: rgb(109, 61, 255); } .input .input__clear { position: absolute; top: 6px; right: 6px; font-size: 18px; color: red; } .input .input__clear:hover { cursor: pointer; } </style>
Agora abra o arquivo App.js
e substitua todo o código pelo código abaixo:
<template> <div id="app"> <div class="component-box"> <search-input /> </div> </div> </template> <script> import SearchInput from './components/search-input/index.vue' export default { name: 'App', components: { SearchInput } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; } .component-box { width: 30%; } </style>
Agora rode a aplicação com o comando npm run serve
e verifique se obteve um resultado semelhante ao da imagem abaixo:
Implementando os Testes
Agora finalmente podemos começar a implementar os testes a medida que implementamos as funcionalidades do componente. Para começar crie um diretório chamado __tests__
dentro do diretório search-input
, destro deste novo diretório crie um arquivo chamado search-input.spec.js
.
Para começar a implementação do primeiro teste copie o código abaixo e cole dentro do arquivo de teste:
import { mount } from '@vue/test-utils' import SearchInput from '../index.vue' describe('search-input - Unit', () => { it('should be a vue instance', () => { const wrapper = mount(SearchInput) expect(wrapper.vm).toBeDefined() }) })
precisamos importar o método mount
de dentro da lib vue-test-utils
, pois é ele que nos permite montar o componente e interagir com ele dentro dos testes. O describe
define o início de uma nova suíte de testes. O método it
define o primeiro teste da suíte. Na primeira linha do it
utilizamos o mount
para carregar o componente em memória e em seguida utilizamos o expect()
do Jest para testar se o componente foi montado corretamente
Obs.: caso queira executar um teste isolado dentro de uma suíte de testes substitua o método it pelo fit
Para ver os testes rodando no seu terminal execute o comando abaixo:
npm run test:watch
Agora vamos voltar no App.vue
e criar uma variável que será utilizada como v-model no componente search-input:
<template> ... <search-input v-model="search" /> </template> <script> ... data() { return { search: '' } } </script>
Vamos alterar o componente para receber a prop que foi passada como v-model e uma computed property que será atualizada quando a prop for alterada e emitirá o evento de input
sempre que algo for digitado no input:
<template> <div class="input"> <input type="text" placeholder="Pesquisar..." v-model="searchQuery" /> <span class="input__clear">×</span> </div> </template> <script> export default { props: { value: { type: String, required: true } }, computed: { searchQuery: { get() { return this.value }, set(val) { this.$emit('input', val) } } } } </script>
Claro que o ideal é que se escreva os testes antes de fazer a implementação, porém quando estamos começando pode ser difícil alcançar este nível de abstração então neste tutorial farei a implementação antes dos testes para facilitar o entendimento.
Como declaramos uma prop que é obrigatória precisamos alterar o nosso primeiro teste para passar uma prop chamada value:
it('should be a vue instance', () => { const wrapper = mount(SearchInput,{ propsData: { value: '' } }) expect(wrapper.vm).toBeDefined() })
Agora que temos parte da lógica do componente implementada vamos escrever mais testes. O próximo teste que vamos escrever será para verificar se o valor de searchQuery
é alterado quando alteramos o valor da prop value
:
it('should update searchQuery when prop value is changed', async () => { const wrapper = mount(SearchInput,{ propsData: { value: '' } }) await wrapper.setProps({ value: 'test' }) await wrapper.vm.$nextTick() expect(wrapper.vm.searchQuery).toEqual('test') })
Aqui utilizamos o método setProps
para atualizar o valor da prop value e em seguida utilizamos o $nextTick
para indicar que é preciso esperar até o próximo ciclo de vida para continuar a execução do teste.
O terceiro teste verificará se o evento de input
é emitido quando algo é digitado no elemento input:
it('should emit input event when something is typed', async () => { const wrapper = mount(SearchInput,{ propsData: { value: '' } }) const inputEl = wrapper.find('input[type="text"]') await inputEl.setValue('test') expect(wrapper.emitted().input).toBeTruthy() expect(wrapper.emitted().input[0]).toEqual(['test']) })
Neste teste utilizamos o método find
para obter o elemento de input e então utilizamos o setValue
para atribuir um valor ao elemento. Após preparar o cenário do teste utilizamos o método emitted
para verificar se o evento de input foi disparado.
Agora que você já se familiarizou com a ideia de escrever testes unitários que tal começar a implementar os testes antes da funcionalidade?
Vamos criar um teste para verificar se o valor do input é limpo quando clicamos no ícone de X:
it('should clear input value when X icon is clicked', async () => { const wrapper = mount(SearchInput,{ propsData: { value: '' } }) const clearBtn = wrapper.find('.input__clear') await clearBtn.trigger('click') expect(wrapper.emitted().input).toBeTruthy() expect(wrapper.emitted().input[0]).toEqual(['']) })
Neste teste utilizamos o método find
para acessar o botão de limpar e em seguida utilizamos o trigger
para simular o evento de click no botão. Por último verificamos se o evento de input foi disparado com uma string vazia.
Obviamente o teste irá falhar já que não implementamos a funcionalidade. Para fazer o teste passar vamos implementar a funcionalidade de limpar o valor:
<template> ... <span class="input__clear" @click="clearValue()"> × </span> </template> <script> ... methods: { clearValue() { this.$emit('input', '') } } </script>
Conclusão
Se você chegou até aqui deve ter um resultado igual a este:
<template> <div class="input"> <input type="text" placeholder="Pesquisar..." v-model="searchQuery" /> <span class="input__clear" @click="clearValue()">×</span> </div> </template> <script> export default { props: { value: { type: String, required: true } }, computed: { searchQuery: { get() { return this.value }, set(val) { this.$emit('input', val) } } }, methods: { clearValue() { this.$emit('input', '') } } } </script> <style scoped> .input { display: flex; width: 100%; position: relative; } .input input { width: 100%; padding: 8px 22px 8px 10px; font-size: 1rem; border: 1px solid #e3e3e3; background: #fafafa; border-radius: 6px; outline: none; } .input input:focus { border-color: rgb(109, 61, 255); } .input .input__clear { position: absolute; top: 6px; right: 6px; font-size: 18px; color: red; } .input .input__clear:hover { cursor: pointer; } </style>
import { mount } from '@vue/test-utils' import SearchInput from '../index.vue' describe('search-input - Unit', () => { it('should be a vue instance', () => { const wrapper = mount(SearchInput,{ propsData: { value: '' } }) expect(wrapper.vm).toBeDefined() }) it('should update searchQuery when prop value is changed', async () => { const wrapper = mount(SearchInput,{ propsData: { value: '' } }) await wrapper.setProps({ value: 'test' }) await wrapper.vm.$nextTick() expect(wrapper.vm.searchQuery).toEqual('test') }) it('should emit input event when something is typed', async () => { const wrapper = mount(SearchInput,{ propsData: { value: '' } }) const inputEl = wrapper.find('input[type="text"]') await inputEl.setValue('test') expect(wrapper.emitted().input).toBeTruthy() expect(wrapper.emitted().input[0]).toEqual(['test']) }) it('should clear input value when X icon is clicked', async () => { const wrapper = mount(SearchInput,{ propsData: { value: '' } }) const clearBtn = wrapper.find('.input__clear') await clearBtn.trigger('click') expect(wrapper.emitted().input).toBeTruthy() expect(wrapper.emitted().input[0]).toEqual(['']) }) })
E assim chegamos ao fim deste tutorial, espero que tenha gostado ;)
Caso queira aprender mais sobre o Vue Test Utils pode dar uma conferida nos guias da própria documentação
Top comments (0)