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

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

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

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

``` js
const state = { ... }

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

export default new Vuex.Store({
  state,
  mutations
})
```

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

``` js
// mutations.js
export const mutations = {
  increment: state => state.count++
}
```

``` js
// 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](https://github.com/plasticine/inject-loader) для сборки файлов тестов.

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

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

export const getAllProducts = ({ commit }) => {
  commit('REQUEST_PRODUCTS')
  shop.getProducts(products => {
    commit('RECEIVE_PRODUCTS', products)
  })
}
```

``` js
// 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(mutation.type).to.equal(type)
      if (payload) {
        expect(mutation.payload).to.deep.equal(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)
  })
})
```

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

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

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

``` js
// getters.js
export const getters = {
  filteredProducts (state, { filterCategory }) {
    return state.products.filter(product => {
      return product.category === filterCategory
    })
  }
}
```

``` js
// 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`](https://babeljs.io/docs/usage/babelrc/)):

``` js
// 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/
      }
    ]
  }
}
```

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

``` bash
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](https://vue-loader.vuejs.org/ru/workflow/testing.html).
