Тестирование

В основном предметом модульного тестирования во Vuex являются мутации и действия.

Тестирование мутаций

Мутации тестировать довольно просто, так как они представляют из себя всего лишь простые функции, поведение которых полностью зависит от переданных параметров. Один трюк заключается в том, что если использовать модули ES2015 и помещать мутации в файле store.js, то помимо экспорта по умолчанию, необходимо экспортировать мутации с помощью именованного экспорта:

const state = { ... }

// именованный экспорт `mutations` отдельно от самого хранилища
export const mutations = { ... }

export default createStore({
  state,
  mutations
})

Пример тестирования мутаций с использованием Mocha + Chai (хотя можно использовать и любые другие библиотеки):

// mutations.js
export const mutations = {
  increment: state => state.count++
}
// mutations.spec.js
import { expect } from 'chai'
import { mutations } from './store'

// деструктурирующее присваивание из `mutations`
const { increment } = mutations

describe('mutations', () => {
  it('INCREMENT', () => {
    // фиксируем состояние
    const state = { count: 0 }
    // применяем мутацию
    increment(state)
    // оцениваем результат
    expect(state.count).to.equal(1)
  })
})

Тестирование действий

Действия тестировать несколько сложнее, поскольку они могут обращаться к внешним API. При тестировании действий обычно приходится заниматься подделкой внешних объектов — например, вызовы к API можно вынести в отдельный сервис, и в рамках тестов этот сервис подменить поддельным. Для упрощения имитации зависимостей можно использовать webpack и inject-loader для сборки файлов тестов.

Пример тестирования асинхронного действия:

// actions.js
import shop from '../api/shop'

export const getAllProducts = ({ commit }) => {
  commit('REQUEST_PRODUCTS')
  shop.getProducts(products => {
    commit('RECEIVE_PRODUCTS', products)
  })
}
// actions.spec.js

// для inline-загрузчиков используйте синтаксис require
// и inject-loader, возвращающий фабрику модулей, помогающую
// подменять зависимости
import { expect } from 'chai'
const actionsInjector = require('inject-loader!./actions')

// создаём поддельную зависимость
const actions = actionsInjector({
  '../api/shop': {
    getProducts(cb) {
      setTimeout(() => {
        cb([/* поддельный ответ от сервера */])
      }, 100)
    }
  }
})

// вспомогательная функция для тестирования действия, которое должно вызывать известные мутации
const testAction = (action, payload, state, expectedMutations, done) => {
  let count = 0

  // поддельная функция вызова мутаций
  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()
    }
  }

  // вызываем действие с поддельным хранилищем и аргументами
  action({ commit, state }, payload)

  // проверяем, были ли инициированы мутации
  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: {
            /* поддельный ответ */
          }
        }
      ],
      done
    )
  })
})

Если есть шпионы (spies), доступные в тестовой среде (например, через Sinon.JS), можно использовать их вместо вспомогательной функции 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',
        {
          /* поддельный ответ */
        }
      ]
    ])
  })
})

Тестирование геттеров

Геттеры, занимающиеся сложными вычислениями, тоже неплохо бы тестировать. Как и с мутациями, тут всё просто:

Пример тестирования геттера:

// 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', () => {
    // поддельное состояние
    const state = {
      products: [
        { id: 1, title: 'Apple', category: 'fruit' },
        { id: 2, title: 'Orange', category: 'fruit' },
        { id: 3, title: 'Carrot', category: 'vegetable' }
      ]
    }
    // поддельный параметр геттера
    const filterCategory = 'fruit'

    // получаем результат выполнения тестируемого геттера
    const result = getters.filteredProducts(state, { filterCategory })

    // проверяем результат
    expect(result).to.deep.equal([
      { id: 1, title: 'Apple', category: 'fruit' },
      { id: 2, title: 'Orange', category: 'fruit' }
    ])
  })
})

Запуск тестов

Если должным образом соблюдаете правила написания мутаций и действий, результирующие тесты не должны зависеть от API браузера. Поэтому их можно просто собрать webpack'ом и запустить в Node. С другой стороны, можно использовать mocha-loader или Karma + karma-webpack, и запускать тесты в реальных браузерах.

Запуск в Node

Используйте следующую конфигурацию webpack (в сочетании с соответствующим .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/
      }
    ]
  }
}

Затем в терминале:

webpack
mocha test-bundle.js

Запуск в браузерах

  1. Установите mocha-loader
  2. Измените entry в приведённой выше конфигурации Webpack на 'mocha-loader!babel-loader!./test.js'.
  3. Запустите webpack-dev-server, используя эту конфигурацию
  4. Откройте в браузере localhost:8080/webpack-dev-server/test-bundle.

Запуск в браузерах при помощи Karma и karma-webpack

Обратитесь к документации vue-loader.