Skip to content

Instantly share code, notes, and snippets.

@marquesm91
Last active April 17, 2024 20:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save marquesm91/0b496617bb588818d32634d90b6e9ddc to your computer and use it in GitHub Desktop.
Save marquesm91/0b496617bb588818d32634d90b6e9ddc to your computer and use it in GitHub Desktop.
Configuração de Testes (unit + e2e) para projetos Vue + Quasar

Configuração de testes unitários (unit)

Será usado o jest para testes unitários de frontend. É uma lib bem documentada, completa e existem diversos exemplos na internet para facilitar no aprendizado.

Para isso, instalaremos o Jest e mais algumas libs extras para permitir testes com o Vue, além de alguns extras para melhorar a experiência durante o desenvolvimento.

# Libs necessárias para rodar testes unitários com Vue e Jest
npm install --save-dev jest jest-serializer-vue babel-jest @babel/core @babel/preset-env vue-jest babel-core@bridge jest-watch-typeahead identity-obj-proxy

As outras lib nos ajudarão em:

  • jest-serializer-vue: Permitir que possamos usar os comandos para gerar snapshots caso seja necessário.
  • babel-jest: Obrigatório para transpilar arquivos *.js para que o Jest consiga entender e aplicar os testes. Para ter o comportamento desejado em qualquer versão do EcmaScript, será adicionado também as libs @babel/core e @babel/preset-env.
  • vue-jest: Seguindo o mesmo princípio do babel-jest, mas para arquivos *.vue. Como dependência dessa lib, a babel-core@bridge precisa ser instalada também.
  • jest-watch-typeahead: Para facilitar a busca de nomes de arquivos teste ou nomes de teste durante o desenvolvimento. Os comandos estarão disponíveis na interface do jest --watch com os atalhos p e t.
  • identity-obj-proxy: Jest trabalha muito bem com arquivos javascript e não entende muito bem CSS puro. Para isso, é necessário mapear os imports dessa natureza e transformá-los em módulos de CSS graças a essa lib.

O Jest precisa de um arquivo inicial de configuração para que entenda em como proceder com relação as transformações de código, mapeamento de módulos, etc. Para isso, basta criar um arquivo na raiz do projeto chamado jest.config.js com a seguinte configuração.

module.exports = {
  roots: ['<rootDir>/src'],
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/test/setupTests.js'],
  globals: {
    __DEV__: true,
  },
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.js',
    '<rootDir>/src/**/*.{spec,test}.js',
  ],
  moduleFileExtensions: ['vue', 'js', 'json'],
  moduleNameMapper: {
    '^@testing-library/quasar$': '<rootDir>/src/test/utils/index.js',
    '^src/(.*)$': '<rootDir>/src/$1',
    '^test-utils/(.*)$': '<rootDir>/src/test/utils/$1',
    '^components/(.*)$': '<rootDir>/src/components/$1',
    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
      'identity-obj-proxy',
  },
  transform: {
    '.*\\.js$': 'babel-jest',
    '.*\\.vue$': 'vue-jest',
  },
  transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.js$'],
  snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname',
  ],
  collectCoverageFrom: [
    '<rootDir>/src/**/*.{js,vue}',
    '!src/**/*.d.ts',
    '!**/node_modules/**',
  ],
  coverageThreshold: {
    // 0-100% coverage
    global: {
      branches: 0,
      functions: 0,
      lines: 0,
      statements: 0,
    },
  },
};

Para começar, podemos definir um arquivo de setup que rodará antes de todos os testes, isto é, antes de executar nossos arquivos de teste, esse arquivo, com seu caminho definido em setupFilesAfterEnv, é o primeiro a ser executado. Ele é comumente usado para preparar os testes definindo alguns mock, importando alguns comandos globalmente, etc.

O testMatch é o regex que defini quais são os arquivos de teste do projeto. Por padrão no jest, todos os arquivos que terminem com *.test.js ou *.spec.js ou que existem dentro de uma pasta __tests__ com a extensão *.js serão nossos arquivos alvos.

O moduleNameMapper nos ajuda a entender onde estão os módulos com caminho não relativo. Para exemplificar, o módulo de nome @testing-library/quasar foi criado para apontar para um utilitário de testes de componentes Vue escrito com Quasar no caminho <rootDir>/src/test/utils/index.js.

O transform é o responsável por transpilar nossos códigos *.vue e *.js para que o Jest consiga entender e rodar as suítes de teste. Aliado a ele, o transformIgnorePatterns irá ignorar todos os arquivos e pastas que forem alcançados pelos regexes. Para tal, todos os arquivos dentro da pasta node_modules com extensão *.js serão ignorados.

O snapshotSerializers apontará para o nosso serializador de snapshots que instalamos anteriormente.

Os watchPlugins são alguns plugins do Jest para alterar o seu comportamento. No nosso caso, estamos utilizando os utilitários filename e testname fornecidos pela lib jest-watch-typeahead.

Por fim os collectCoverageFrom e coverageThreshold são as configurações necessárias para explicar ao Jest quais são os arquivos que queremos cobrir com uma suíte de testes e qual o percentual desejado.

Utilizando a @testing-library

Para melhorar a forma como testamos e ter testes mais legíveis, o ecossistema de libs @testing-library será usado.

# Libs necessárias para rodar testes unitários com Vue e @testing-library
npm install --save-dev @testing-library/vue @testing-library/user-event @testing-library/jest-dom
  • @testing-library/vue: Todo o utilitário necessário para montar nossos componentes Vue nos testes.
  • @testing-library/user-event: Uma abstração do fireEvent em @testing-library/vue. Essa lib é muito boa para simular eventos precisos de usuários como click em um botão (disparando eventos de focus, blur, além do click), ou digitar alguma coisa em um input.
  • @testing-library/jest-dom: Essa lib entrega mais alguns métodos de asserção para tornar nossos testes ainda mais legíveis.

Após instalar essas libs para criar nossos testes em Vue, vamos criar um arquivo em src/test com o nome setupTests.js

import '@testing-library/jest-dom/extend-expect';

Esse arquivo ficará responsável por inicializar todos os métodos extras de assert em @testing-library/jest-dom em nossos testes.

Além disso também é necessário adicionar na raiz do projeto o arquivo babel.config.js com a seguinte configuração para ambiente de testes.

module.exports = {
  ...
  env: {
    test: {
      presets: [
        [
          '@babel/preset-env',
          {
            targets: {
              node: 'current',
            },
          },
        ],
      ],
    },
  },
};

Criando o módulo @testing-library/quasar

Para facilitar os testes que utilizam o framework Quasar além do Vue é necessário criar um utilitário em src/test/utils com os arquivos index.js e Template.vue. Isso nos ajudará a abstrair algumas ações necessárias antes de montar nossos componentes com Quasar e manter o padrão da interface da lib @testing-library/vue.

Template.vue

<template>
  <q-layout>
    <q-page-container>
      <component :is="children" />
    </q-page-container>
  </q-layout>
</template>

<script>
  export default {
    name: 'Template',
    props: {
      children: {
        type: Object,
        required: true,
      },
    },
  };
</script>

index.js

import { createLocalVue } from '@vue/test-utils';
import { render as testingRender } from '@testing-library/vue';
import Template from './Template.vue';
import * as All from 'quasar';

const { Quasar } = All;
const localVue = createLocalVue();

localVue.use(Quasar, {
  components: Object.keys(All).reduce((acc, key) => {
    const val = All[key];

    if (val && val.component && val.component.name !== null) {
      acc[key] = val;
    }

    return acc;
  }, {}),
});

export * from '@testing-library/vue';

// Adapt @testing-library/vue render to use Quasar and Vue
export function render(component, config) {
  config = config || {};

  if (config.store) {
    addStore(config);
  }

  if (config.routes) {
    addRouter(config);
  }

  removeProps(config, undefined);

  // Rendering component within Template because every
  // quasar page component needs to be encapsulated by
  // q-page and q-page-container
  return testingRender(Template, {
    localVue,
    propsData: { children: component },
    ...config,
  });
}

function addStore(config) {
  const Vuex = require('vuex');

  localVue.use(Vuex);
  // https://github.com/testing-library/vue-testing-library/blob/88cee6f00e7f28021cb098a57f2681c8caabad78/src/vue-testing-library.js#L35
  // @testing-library/vue will instanciate new Vuex.Store(config.store);
  // config.store = new Vuex.Store(config.store);
}

function addRouter(config) {
  const VueRouter = require('vue-router');

  localVue.use(VueRouter);
  // https://github.com/testing-library/vue-testing-library/blob/88cee6f00e7f28021cb098a57f2681c8caabad78/src/vue-testing-library.js#L41
  // @testing-library/vue will instanciate new VueRouter({ routes: config.routes });
  // config.router = new VueRouter({ routes: config.routes });
}

function removeProps(config, prop) {
  Object.keys(config).forEach(key => {
    if (config.hasOwnProperty(key) && config[key] === prop) {
      delete config[key];
    }
  });
}

Dessa forma conseguimos renderizar componentes que usam o framework Quasar da mesma forma que componentes puros com Vue.

// Ao invés de importar assim
import { render, fireEvent } from '@testing-library/vue';

// Use assim
import { render, fireEvent } from '@testing-library/quasar';

Esse módulo @testing-library/quasar é apenas um mapeamento definido em setupTests.js para o caminho src/test/utils/index.js.

Rodando nossos testes unitários

Dentro do arquivo package.json adicione esses comandos em scripts.

"scripts": {
  "test": "jest --watch",
  "test:coverage": "npm run test -- --coverage --watchAll=false",
}

Configuração de testes de ponta a ponta (e2e)

Assim como a @testing-library ajuda na experiência de desenvolvimento de testes unitários, o mesmo pode se dizer para testes de ponta a ponta. A lib @testing-library/cypress irá nos ajudar nisso.

# Libs necessárias para rodar testes e2e com Vue e @testing-library
npm install --save-dev cypress @testing-library/cypress start-server-and-test
  • cypress: Necessário para rodar todos os comandos cypress e instalar tudo que for necessário para esse tipo de teste.
  • @testing-library/cypress: uma ótima lib para nos fornecer métodos de busca de elementos para melhorar a legibilidade do código.
  • start-server-and-test: Essa lib nos ajudará a esperar pelo servidor responder em uma determinada porta para que nossas testes consigam rodar em modo headless (sem interface por meio de uma aplicação, que é seu comportamento padrão com cypress open). Excelente para situações como pipeline de integração contínua.

Crie um arquivo na raiz do projeto chamado cypress.json e adicione esse conteúdo a ele.

{
  "baseUrl": "http://localhost:3000",
  "integrationFolder": "cypress/e2e",
  "videosFolder": "cypress/medias/videos",
  "screenshotsFolder": "cypress/medias/screenshots"
}

Além dele, crie uma pasta cypress na raiz do projeto também contendo esses arquivos

e2e/App.js

describe('App', () => {
  it('should visit root page', () => {
    cy.visit('/');
  });
});

fixtures/example.json

{}

plugins/index.js

// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)

// cypress/plugins/index.js

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  // console.log(config); // see what all is in here!
};

support/index.js

// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
import './commands';

support/commands.js

// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
import '@testing-library/cypress/add-commands';

Com as pastas e2e, fixtures, plugins, medias e support dentro de cypress temos toda a estrutura necessária para que os testes em cypress funcionem.

A pasta e2e guardará toda a nossa suíte de testes dispostas da forma como preferirmos.

A pasta fixtures é responsável por criar arquivos json que nos ajudarão a fornecer dados estáticos para os nossos testes.

// Dessa forma em um teste conseguimos
// acessar os dados presentes no json
// que está em `fixtures/user.json`
cy.fixture('user.json').then(json => {
  // Faça o que quiser com o conteúdo em user.json
});

A pasta plugins é uma forma de configurar o cypress para ter algum comportamento desejado ou sobrescrever algo que já exista internamente em seu escopo.

A pasta medias guardará os screenshots e as gravações caso optamos por gravar os testes. Essa pasta será ignorada pelo git para que não tenhamos que manipular arquivos binários em seu histórico.

Por fim, a pasta support guarda todos os comandos e arquivos que serão carregados antes de rodarmos nossa suíte de testes. Pense aqui como algo semelhante ao setupTests.js configurado no jest.

Rodando nossos testes de ponta a ponta

Dentro do arquivo package.json adicione esses comandos em scripts.

"scripts": {
  "cy:open": "cypress open",
  "cy:run": "cypress run --config video=false --record false",
  "test:e2e": "start-server-and-test serve 3000 cy:run",
  "serve": "npx serve -s build -p 3000"
}

Durante o desenvolvimento, o comando cy:open precisará de sua aplicação rodando na porta 3000, como configurado em cypress.json, para que os testes consigam ser rodados.

O comando cy:run tem o mesmo comportamento de cy:open e também precisa de uma aplicação rodando na porta 3000. Sua única diferença é que irá rodar em modo headless e não irá gravar nada ou tirar prints.

Para um ambiente de pipeline de testes, os comandos test:e2e e serve foram criados. Uma aplicação na porta 3000 é servida para os testes e o comando para executá-los só começará quando essa mesma aplicação estiver respondendo, graças a lib start-server-and-test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment