• #front-end
  • #vue
  • #tests

Como testar plugins no Vuejs?

Publicado em set, 13 2021


Recentemente aqui no trabalho precisávamos criar um plugin para a nossa aplicação Vue.js. Como eu gosto de pensar em testes sempre, logo levantei a bola para que tal plugin tivesse testes. Mas como criar testes para os nossos plugins? Neste texto eu gostaria de compartilhar algumas abordagens e, passo a passo, veremos como testar nossos plugins Vue.

Para essa tarefa nós vamos usar o Vue Test Utils. Eu já escrevi sobre ele aqui no blog e creio que ele seja a melhor lib para nos auxiliar nessa tarefa, pois iremos interagir com nossos componentes num baixo nível, testando se o plugin que criamos faz o que esperamos que ele faça.

Nota: tanto o código quanto o plugin estão usando a versão 2 do nosso querido framework, o conceito se mantém para o Vue 3, mas a implementação não.

O plugin

A seguir, mostro o código do plugin que iremos testar:

import pkg from 'path/to/package.json'

export default {
  install (Vue) {
    Vue.prototype.$_appVersion = pkg.version

    Vue.prototype.$_pick = (keys = [], original = {}) => {
      return keys.reduce((acc, currentKey) => {
        acc[currentKey] = original[currentKey]
        return acc
      }, {})
    }

    Vue.prototype.$_omit = (keys = [], original = {}) => {
      return Object.keys(original).reduce((acc, currentKey) => {
        if (!keys.includes(currentKey)) {
          acc[currentKey] = original[currentKey]
        }

        return acc
      }, {})
    }

    Vue.directive('click', {
      bind (el, binding) {
        el.addEventListener('click', (event) => {
          binding.value(event)
        })
      }
    })

    Vue.filter('toUpper', (value = '') => value.toUpperCase())
  }
}

O conceito é simples: o plugin será usado para adicionar alguns métodos e propriedades que serão compartilhados com todos os componentes Vue. Iremos testar também a diretiva personalizada click que nós criamos e também o filtro toUpper.

Antes de começarmos a descriminar os testes, precisamos criar uma instância local do Vue, instalar nosso plugin nela e usarmos essa instância para os nossos testes. Para não precisar repetir esse processo, crio uma função factory cujo objetivo é encapsular essas etapas retornar uma instância de mount.

import { mount, createLocalVue } from '@vue/test-utils'
import ApplicationPlugin from 'path/to/plugin'

const localVue = createLocalVue()

localVue.use(ApplicationPlugin)

const factory = () => {
  const component = {
    name: 'TestComponent',
    template: '<div></div>'
  }

  return mount(component, {
    localVue
  })
}

Testando uma propriedade

A seguir, testamos se o nosso plugin adiciona corretamente a versão do package.json na instância do componente:

it('should the version number be available from $_appVersion property', () => {
  const wrapper = factory()

  expect(wrapper.vm.$_appVersion).toBe(pkg.version)
})

Primeiramente, criamos uma variável com o resultado de mount, e depois checamos se a instância do componente possui a propriedade $_appVersion. Fazemos isso através de wrapper.vm.

Testando uma função

Para testamos uma função, o processo é o mesmo, a diferença está no fato de que precisamos verificar se a função executa o que esperamos dela. A seguir, os casos de teste para as funções $_pick e $_omit.

it('should the $_pick method should be available', () => {
  const wrapper = factory()

  const testObject = {
    name: 'John',
    lastName: 'Doe',
    age: 30
  }

  expect(wrapper.vm.$_pick).toBeInstanceOf(Function)
  expect(wrapper.vm.$_pick(['name', 'lastName'], testObject)).toEqual({
    name: testObject.name,
    lastName: testObject.lastName
  })
})

it('should the $_omit method should be available', () => {
  const wrapper = factory()

  const testObject = {
    name: 'John',
    lastName: 'Doe',
    age: 30
  }

  expect(wrapper.vm.$_omit).toBeInstanceOf(Function)
  expect(wrapper.vm.$_omit(['name', 'lastName'], testObject)).toEqual({
    age: testObject.age
  })
})

Testando uma diretiva

O teste de uma diretiva também é parecido, porém, não há uma forma exata. Para a diretiva que criamos acima, posso testar criando um componente que faz uso dela e mockando o método que eu espero que seja chamado dado a interação do usuário que a diretiva em questão captura.

it('should install the v-click directive', async () => {
  const handleClickMock = jest.fn()

  const component = {
    name: 'TestComponent',
    template: '<button v-click="handleClick"> Test button </button>',
    methods: {
      handleClick: handleClickMock
    }
  }

  const wrapper = mount(component, {
    localVue
  })

  await wrapper.find('button').trigger('click')

  expect(handleClickMock).toHaveBeenCalled()
})

Testando um filtro

Por fim, temos o teste para o filter. Aqui iremos seguir a mesma lógica do teste para a diretiva: vamos criar um componente simulando um uso real do filtro e esperando que ela seja processada corretamente.

it('should install the toUpper directive', async () => {
  const component = {
    name: 'TestComponent',
    template: '<p> {{ message | toUpper }} </p>',
    data () {
      return {
        message: 'Hello world'
      }
    }
  }

  const wrapper = mount(component, {
    localVue
  })

  expect(wrapper.find('p').text()).toBe('HELLO WORLD')
})

Mudanças para Vue.js 3

No início do post é mencionado que o código acima não se aplica ao Vue 3. O código do nosso plugin precisa ser alterado devido a algumas breaking changes que temos nessa versão:

  • A função install de um plugin não recebe mais, como primeiro parâmetro, a instância do Vue, mas sim, a instância de app. Mais informações, você pode conferir na documentação oficial;

  • filters foram removidos. Para termos uma funcionalidade parecida, podemos usar computeds;

  • Não é mais possível modificar o prototype do Vue, com Vue.prototype, para adicionar funções ou propriedades. Para termos a mesma funcionalidade, podemos criar propriedades globalmente, com global.config.globalProperties.

Dito isso, o código do nosso plugin ficaria assim:

import pkg from 'path/to/package.json'

export default {
  install (app) {
    app.config.globalProperties.$_appVersion = pkg.version

    app.config.globalProperties.$_pick = (keys = [], original = {}) => {
      return keys.reduce((acc, currentKey) => {
        acc[currentKey] = original[currentKey]
        return acc
      }, {})
    }

    app.config.globalProperties.$_omit = (keys = [], original = {}) => {
      return Object.keys(original).reduce((acc, currentKey) => {
        if (!keys.includes(currentKey)) {
          acc[currentKey] = original[currentKey]
        }

        return acc
      }, {})
    }

    app.directive('click', {
      beforeMount (el, binding) {
        el.addEventListener('click', (event) => {
          binding.value(event)
        })
      }
    })
  }
}

E os nossos testes, como ficam?

Bem, com relação aos testes, algumas coisas precisam mudar.

Primeiro, com a nova versão do Vue Test Utils, a api de createLocalVue está depreciada. Agora, para "instalarmos" nosso plugin no componente que estamos usando, precisamos passar o plugin como uma configuração global. A nossa função factory poderia ficar assim:

import { mount } from '@vue/test-utils'
import ApplicationPlugin from '@/plugin'

const factory = () => {
  const component = {
    name: 'TestComponent',
    template: '<div></div>'
  }

  return mount(component, {
    global: {
      plugins: [ApplicationPlugin]
    }
  })
}

Uma outra mudança é que como não mais temos filters, o código de teste para filters não se torna mais necessário. Porém, é possível refatorar os locais que usam o filter toUpper, e criar um método global, por exemplo, $_toUpper, e usar esse método em computeds, como mencionado acima.


Bem pessoal, ficamos por aqui. Espero que este texto tenha sido útil para você. Se curtiu, não esqueça de compartilhá-lo nas suas redes sociais. Até a próxima!


Imagem de fundo criada por Markus Winkler, download pelo Unsplash.