Teste seus componentes com Vue Test Utils
Tags: vue, tests
Published: 2021-09-10 12:37:46

Fala! Hoje gostaria de trazer o assunto de testes com Vue.js. Depois de alguns meses trabalhando na criação de uma biblioteca de componentes de maneira intensiva, e agora, trabalhando na migração do produto principal aqui da empresa, gostaria de compartilhar alguns pensamentos e opiniões a respeito de algumas abordagens de testes com Vue.js e o uso de bibliotecas que nos auxiliam na hora de implementá-los.

O objetivo é poder discutir o uso do [Vue Test Utils](https://vue-test-utils.vuejs.org/) e do [Vue Testing Library](https://testing-library.com/docs/vue-testing-library), vantagens e desvantagens de usar um ou outro e quem sabe, num terceiro momento, trazer o uso do [Cypress Component Testing](https://docs.cypress.io/guides/component-testing/introduction), que promete bastante.

Antes de mais nada, você precisa saber que os códigos deste post estarão neste repositório no Github - [vue2-testing-blog](https://github.com/emanuelgsouza/vue2-testing-blog). Dá aquela força dando um _star_ no projeto e compartilhando.

Sem mais delongas, vamos ao conteúdo de hoje. Como testar nossos componentes Vue.js com Vue Test Utils!

-----

Primeiramente, vou pular a parte da instalação do Vue Test Utils (daqui para a frente vou chamá-lo de **VTU**), pois normalmente se você inicia um projeto com o [Vue CLI](https://cli.vuejs.org/) e escolhe entre as opções, que irá ter testes unitários, você vai perceber que este pacote será instalado por padrão. Porém, caso você precise de um guia para a instalação, você pode conferir a seção ["Instalação" na documentação oficial](https://vue-test-utils.vuejs.org/installation/#installation).

## Conhecendo um pouco do VTU

Quando vamos testar nossos componentes Vue.js, precisamos, ao menos, executar duas tarefas:

* Buscar elementos dentro do componente
* Interagir com esses elementos

Essas duas tarefas acima tem como objetivo testar se, dado uma interação com um componente, ele executa o que a gente espera dele. Vamos a um exemplo extraído da [documentação do VTU](https://vue-test-utils.vuejs.org/guides/#getting-started)

```js
// nosso componente Counter
const Counter = {
  template: `
    <div>
      <button @click="count++">Add up</button>
      <p>Total clicks: {{ count }}</p>
    </div>
  `,
  data() {
    return { count: 0 }
  }
}

// nossa suíte de testes
test('increments counter value on click', async () => {
  const wrapper = mount(Counter)

  // busca elementos para interação
  const button = wrapper.find('button')
  const text = wrapper.find('p')

  // executa uma asserção para ter um valor inicial
  expect(text.text()).toContain('Total clicks: 0')

  // interage com um elemento
  await button.trigger('click')

  // testa se ocorre o resultado esperado
  expect(text.text()).toContain('Total clicks: 1')
})
```

Como você pode ver, executamos as duas tarefas e assim testamos o nosso componente de maneira satisfatória.

Você pode encontrar mais informações sobre essa biblioteca no seu [site oficial](https://vue-test-utils.vuejs.org/guides/#getting-started) e encontrar um conjunto de boas práticas no [Vue Testing Handbook](https://lmiller1990.github.io/vue-testing-handbook/), criado pelo autor do VTU.

-----

Depois dessa breve introdução, gostaria de compartilhar um pouco das minhas reflexões com relação à essa biblioteca. Já gostaria de adiantar que **são minhas opiniões** depois de algum tempo usando bastante ela, seja no contexto de uma biblioteca de componentes, seja no contexto de uma aplicação real em construção.

### Biblioteca oficial para testes

Uma das coisas que mais incomodam quando vamos usar bibliotecas, plugins ou qualquer outra coisa em conjunto com nosso framework e estamos num processo de migração deste é o suporte que tais pacotes de terceiros irão ter com o framework que estamos utilizando.

Sem dúvida, uma das melhores partes de se usar o VTU é que você vai estar usando uma ferramenta que é mantida pelo _core team_ do Vue.js. Isso significa que qualquer alteração no Vue.js terá impactos e "rapidamente" será propagado para o VTU.

### Interação com o componente a baixo nível

Aqui vem uma questão polêmica: **o que seria um teste unitário no contexto de aplicações componentizadas que temos hoje em dia?** Seria testar uma funcionalidade específica do meu sistema? Seria testar apenas um componente específico, isoladamente?

Se sua resposta a última pergunta foi sim, o VTU é perfeito para você. Ao usarmos a função `mount`, ela retorna uma instância de um componente, e assim podemos fazer um genuíno teste unitário caixa branca, pois podemos trabalhar com um componente a baixo nível e testar seu comportamento esperado nos mínimos detalhes.

Mas, o que eu quero dizer com trabalhar com um componente no baixo nível?

Se lembra que eu comentei que a função `mount` retorna uma instância do componente? Por essa instância, eu tenho acesso as propriedades do meu componente (`data`, `computeds` e `props`), bem como aos métodos dele. Sendo assim, imagine que meu componente tenha a seguinte estrutura:

```js
// omitindo o template de propósito
export default {
  data: () => ({
    model: {
      name: null,
      last_name: null,
      position: null
    }
  }),

  computed: {
    canSave () {
      return (
        this.isNotEmpty(this.model.name) &&
        this.isNotEmpty(this.model.last_name) &&
        this.isNotEmpty(this.model.position)
      )
    }
  },

  methods: {
    isNotEmpty (value) {
      if (typeof value === 'string') {
        return value.length > 0
      }

      return value !== null
    }
  }
}
```

Agora eu quero testar que, se eu alterar o `model.name`, e o `model.last_name`, a computed `canSave` ainda precisa ser `false`, pois ainda não tenho um valor para `model.position`. E eu consigo fazer isso sem muito esforço:

```js
// ignorando importação e montagem do componente
it('should keep the canSave computed as false even if name and last_name are filled', () => {
  wrapper.vm.model.name = 'John'
  wrapper.vm.model.last_name = 'Doe'

  expect(wrapper.vm.canSave).toBe(false)
})
```

Eu também posso testar separadamente meu método `isNotEmpty`:

```js
it('should return the correct values for the vm.isNotEmpty() method', () => {
  const wrapper = mount(Form)

  expect(wrapper.vm.isNotEmpty('')).toBe(false)
  expect(wrapper.vm.isNotEmpty(null)).toBe(false)
  expect(wrapper.vm.isNotEmpty('test')).toBe(true)
})
```

Como consequência dessa interação a baixo nível, eu posso não testar a interação do meu usuário com o componente que eu estou testando. Um exemplo: imagine que você tem um componente de login que possui um método `login` (bem criativo 😅). Você pode testar o método `login` separadamente. Porém, eu acredito ser melhor testar a interação do usuário com esse método, por exemplo, testando se ao clicar no botão de Login, o método `login` é chamado e executa a chamada à API com os parâmetros esperados.

Uma outra consequência dessa interação a baixo nível é que se torna um pouco complexo usar o VTU para realizar buscas por elementos dentro do componente.

Pegue como exemplo, este [simples componente de formulário](https://github.com/emanuelgsouza/vue2-testing-blog/blob/main/src/components/Form.vue). Imagine que eu precise testar o preenchimento de cada campo do formulário e ao final, precise clicar no botão de Salvar e esperar que o componente emita o que espero dele.

Imaginou? Uma possível solução seria essa:

```js
it('should properly fill the model data', async () => {
  const wrapper = mount(Form)

  const name = 'John'
  const lastName = 'Doe'
  const expectedModel = {
    name,
    last_name: lastName,
    position: 'front'
  }

  await wrapper.find('[data-testid="form-name"] input').setValue(name)

  await wrapper
    .find('[data-testid="form-last_name"] input')
    .setValue(lastName)

  await wrapper
    .find('[data-testid="form-position"] select')
    .findAll('option')
    .at(0)
    .setSelected()

  expect(wrapper.vm.model).toEqual(expectedModel)

  await wrapper.find('[data-testid="btn-save"]').trigger('click')

  expect(wrapper.emitted('save')[0][0]).toEqual(expectedModel)
})
```

Aqui eu uso `data-testids` - uma [boa prática para desacoplar seus testes do código em si](https://docs.cypress.io/guides/references/best-practices#Selecting-Elements) - para facilitar essa busca, porém, este problema nos leva a pelo menos dois pontos negativos ao usarmos o VTU.

### Não é fluído testar componentes complexos

Uma das minhas maiores dores com o VTU é buscar os elementos para interação, porém, eu preciso reconhecer que isso é uma característica da biblioteca. Ao mesmo tempo que é fácil buscar um componente filho na minha instância e interagir com ele, meu código de teste fica verboso e bastante acoplado à implementação.

Em outras palavras, eu só tenho duas possibilidades de buscar elementos no meu componente: usando um seletor HTML (método [find](https://vue-test-utils.vuejs.org/api/wrapper/#find) e afins) ou buscando pelo componente em si (usando o método [findComponent](https://vue-test-utils.vuejs.org/api/wrapper/#findcomponent) e afins).

Porém, você talvez concorde que estar acoplado a implementação neste caso não é ruim, na verdade é esperado, pois como estou testando _"unitariamente"_ meu componente, é esperado que eu saiba nos mínimos detalhes como ele funciona, e queira garantir através dos testes seu funcionamento.

### É difícil testar acessibilidade

Não é impossível verificar questões de acessibilidade quando usamos o VTU, porém, a biblioteca não encoraja uma abordagem de testes unitários/de integração que ao mesmo tempo, testem questões de acessibilidade.

Um exemplo de teste de acessibilidade é verificar se os meus itens de formulários estão devidamente "rotulados", ou seja, se existem labels que os identificam. Com VTU isso é um pouco complexo. Eu precisaria saber o `id` de cada elemento, e verificar se existe uma label com o correspondente atributo `for`.

Em um outro momento veremos que existe uma outra biblioteca de testes que provê uma série de queries que trazem fluidez na hora que estamos criando nossos testes, e ao mesmo tempo, encoraja em pensarmos um pouco sobre acessibilidade na criação de nossos componentes.

-----

## Resumindo

Se você está a procura de uma biblioteca de auxílio nos testes unitários dos seus componente Vue.js, sem dúvida, o VTU é uma excelente alternativa, pois ele te dará um controle refinado para a criação dos seus testes.

Todavia, é necessário deixar claro que caso o seu componente seja um pouco complexo e tenha interações com outros componentes, será um pouco trabalhoso criar seus testes com o VTU.

## Aonde se aprofundar mais?

* A [documentação do VTU](https://vue-test-utils.vuejs.org/) é sucinta e direta ao ponto, comece por ela.
* Se está a procura de uma abordagem mais "mão na massa", não deixe de conferir o [Handbook do VTU](https://lmiller1990.github.io/vue-testing-handbook/), que traz alguns cases e algumas boas práticas na criação de testes com o VTU.
* Nas minhas pesquisas eu encontrei [este Cheatsheet](https://github.com/dekadentno/vue-unit-testing-cheat-sheet) que achei bem interessante. Pode servir como uma referência.

-----

Espero que este post tenha sido útil para você e que ele possa ter trazido algumas reflexões a respeito dessa excelente biblioteca de testes com Vue.js.

Até a próxima.