Testando
As partes principais que queremos testar unitáriamente no Vuex são mutações e ações.
Testando Mutações
As mutações são muito simples de testar, porque são apenas funções que dependem completamente de seus argumentos. Um truque é que se você estiver usando módulos ES2015 e colocar suas mutações dentro do arquivo store.js
, além da exportação padrão, você também deve exportar as mutações como uma exportação nomeada:
const state = { ... }
// exporta `mutações` como uma exportação nomeada
export const mutations = { ... }
export default createStore({
state,
mutations
})
Exemplo de teste de uma mutação usando Mocha + Chai (você pode usar qualquer biblioteca de framework/assertion que desejar):
// mutations.js
export const mutations = {
increment: state => state.count++
}
// mutations.spec.js
import { expect } from 'chai'
import { mutations } from './store'
// desestrutura `mutações` atribuidas
const { increment } = mutations
describe('mutations', () => {
it('INCREMENT', () => {
// estado mockado (ou simulado)
const state = { count: 0 }
// aplica a mutação
increment(state)
// afirma o resultado
expect(state.count).to.equal(1)
})
})
Testando Ações
As ações podem ser um pouco mais complicadas porque podem chamar as APIs externas. Ao testar ações, geralmente precisamos fazer algum nível de mocking - por exemplo, podemos abistrair as chamadas da API em um serviço e simular (ou mockar (mock)) esse serviço dentro de nossos testes. A fim de simular facilmente as dependências, podemos usar o webpack e inject-loader para empacotar (ou criar um bundle dos) nossos arquivos de teste.
Exemplo de teste de uma ação assíncrona:
// actions.js
import shop from '../api/shop'
export const getAllProducts = ({ commit }) => {
commit('REQUEST_PRODUCTS')
shop.getProducts(products => {
commit('RECEIVE_PRODUCTS', products)
})
}
// actions.spec.js
// use a sintaxe 'require' para inline loaders.
// com inject-loader, isso retorna um factory de módulos
// que nos permite injetar dependências mockadas (ou simuladas).
import { expect } from 'chai'
const actionsInjector = require('inject-loader!./actions')
// cria o módulo com nossos mocks
const actions = actionsInjector({
'../api/shop': {
getProducts (cb) {
setTimeout(() => {
cb([ /* resposta simulada */ ])
}, 100)
}
}
})
// método auxiliar para teste de ação com mutações esperadas
const testAction = (action, payload, state, expectedMutations, done) => {
let count = 0
// confirmação simulada (ou mock commit)
const commit = (type, payload) => {
const mutation = expectedMutations[count]
try {
expect(type).to.equal(mutation.type)
expect(payload).to.deep.equal(mutation.payload)
} catch (error) {
done(error)
}
count++
if (count >= expectedMutations.length) {
done()
}
}
// chame a ação com store mockado (ou simulado) e argumentos
action({ commit, state }, payload)
// verifica se nenhuma mutação deveria ter sido despachada
if (expectedMutations.length === 0) {
expect(count).to.equal(0)
done()
}
}
describe('actions', () => {
it('getAllProducts', done => {
testAction(actions.getAllProducts, null, {}, [
{ type: 'REQUEST_PRODUCTS' },
{ type: 'RECEIVE_PRODUCTS', payload: { /* resposta simulada */ } }
], done)
})
})
Se você tem spies disponíveis em seu ambiente de teste (por exemplo via Sinon.JS), você pode usá-los em vez do método auxiliar testAction
:
describe('actions', () => {
it('getAllProducts', () => {
const commit = sinon.spy()
const state = {}
actions.getAllProducts({ commit, state })
expect(commit.args).to.deep.equal([
['REQUEST_PRODUCTS'],
['RECEIVE_PRODUCTS', { /* resposta simulada */ }]
])
})
})
Testando Getters
Se seus getters tiverem um código complexo, vale a pena testá-los. Os Getters também são muito simples de testar pelo mesmo motivo que as mutações.
Exemplo testando um getter:
// getters.js
export const getters = {
filteredProducts (state, { filterCategory }) {
return state.products.filter(product => {
return product.category === filterCategory
})
}
}
// getters.spec.js
import { expect } from 'chai'
import { getters } from './getters'
describe('getters', () => {
it('filteredProducts', () => {
// estado mockado (ou simulado)
const state = {
products: [
{ id: 1, title: 'Apple', category: 'fruit' },
{ id: 2, title: 'Orange', category: 'fruit' },
{ id: 3, title: 'Carrot', category: 'vegetable' }
]
}
// getter mockado (ou simulado)
const filterCategory = 'fruit'
// obtem o resultado do getter
const result = getters.filteredProducts(state, { filterCategory })
// afirma o resultado
expect(result).to.deep.equal([
{ id: 1, title: 'Apple', category: 'fruit' },
{ id: 2, title: 'Orange', category: 'fruit' }
])
})
})
Executando Testes
Se suas mutações e ações estiverem escritas corretamente, os testes não devem ter dependência direta das APIs do navegador após uma simulação apropriada. Assim, você pode simplesmente empacotar (ou criar um bundle) dos testes com o webpack e executá-lo diretamente no Node. Alternativamente, você pode usar mocha-loader
ou Karma + karma-webpack
para executar os testes em navegadores reais.
Executando no Node
Crie a seguinte configuração de webpack (juntamente com .babelrc
):
// webpack.config.js
module.exports = {
entry: './test.js',
output: {
path: __dirname,
filename: 'test-bundle.js'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
}
}
Então:
webpack
mocha test-bundle.js
Executando no Navegador
- Instale o
mocha-loader
. - Mude o
entry
da configuração do webpack acima para'mocha-loader!babel-loader!./test.js'
. - Inicie o
webpack-dev-server
usando a configuração. - Vá para
localhost:8080/webpack-dev-server/test-bundle
.
Executando no Navegador com Karma + karma-webpack
Consulte a instalação na documentação do vue-loader.