Welker Arantes Ferreira
Posted on October 24, 2021
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
Posted on October 24, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.