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)