Testing logic stores
NOTE! This section of the guide is still heavily under construction! Please check back in a few days.
Generic test setup
For testing kea we recommend using jest.
Resetting the cache before each test
// logic.test.js
import { resetKeaCache } from 'kea'
beforeEach(() => {
resetKeaCache()
})
test('starts from a clear state', () => {
// ...
})
Creating a store for your tests
import { resetKeaCache, keaReducer } from 'kea'
import { keaSaga } from 'kea-saga'
import { createStore, applyMiddleware, combineReducers, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
function getStore () {
resetKeaCache()
const reducers = combineReducers({
scenes: keaReducer('scenes')
})
const sagaMiddleware = createSagaMiddleware()
const finalCreateStore = compose(
applyMiddleware(sagaMiddleware)
)(createStore)
const store = finalCreateStore(reducers)
sagaMiddleware.run(keaSaga)
return store
}
test('do something with the store', () => {
const store = getStore()
// ...
})
Testing logic
/* global expect, test */
import { keaReducer } from 'kea'
import PropTypes from 'prop-types'
import getStore from './helpers/get-store'
test('homepage logic has all the right properties', () => {
const store = getStore()
// we must require because importing makes
const logic = require('./logic').default
expect(logic.path).toEqual(['scenes', 'homepage', 'index'])
// actions
expect(Object.keys(logic.actions)).toEqual(['updateName'])
const { updateName } = logic.actions
expect(typeof updateName).toBe('function')
expect(updateName.toString()).toBe('update name (homepage.index)')
expect(updateName('newname')).toEqual({ payload: { name: 'newname' }, type: updateName.toString() })
// reducers
const defaultValues = { name: 'Chirpy' }
const state = { scenes: { homepage: { index: defaultValues } } }
expect(Object.keys(logic.reducers).sort()).toEqual(['capitalizedName', 'name'])
expect(logic.reducers).toHaveProperty('name.reducer')
expect(logic.reducers).toHaveProperty('name.type', PropTypes.string)
expect(logic.reducers).toHaveProperty('name.value', 'Chirpy')
const nameReducer = logic.reducers.name.reducer
expect(Object.keys(nameReducer)).toEqual([ updateName.toString() ])
expect(nameReducer[updateName.toString()]).toBeDefined()
expect(nameReducer[updateName.toString()]('', { name: 'newName' })).toBe('newName')
expect(logic.reducers).not.toHaveProperty('capitalizedName.reducer')
expect(logic.reducers).toHaveProperty('capitalizedName.type', PropTypes.string)
expect(logic.reducers).not.toHaveProperty('capitalizedName.value', 'chirpy')
// big reducer
expect(typeof logic.reducer).toBe('function')
expect(logic.reducer({}, { type: 'random action' })).toEqual(defaultValues)
expect(logic.reducer({ name: 'something' }, { type: 'random action' })).toEqual({ name: 'something' })
expect(logic.reducer({ name: 'something' }, updateName('newName'))).toEqual({ name: 'newName' })
// selectors
expect(Object.keys(logic.selectors).sort()).toEqual(['capitalizedName', 'name', 'root'])
expect(logic.selectors.name(state)).toEqual('Chirpy')
expect(logic.selectors.capitalizedName(state)).toEqual('Chirpy')
// root selector
expect(logic.selector(state)).toEqual(defaultValues)
expect(logic.selectors.root(state)).toEqual(defaultValues)
})
Testing components with enzyme
/* global test, expect */
import './helper/jsdom'
import React, { Component } from 'react'
import { kea } from 'kea'
import PropTypes from 'prop-types'
import { mount } from 'enzyme'
import { Provider } from 'react-redux'
import getStore from './helper/get-store'
class SampleComponent extends Component {
render () {
const { id, name, capitalizedName } = this.props
const { updateName } = this.actions
return (
<div>
<div className='id'>{id}</div>
<div className='name'>{name}</div>
<div className='capitalizedName'>{capitalizedName}</div>
<div className='updateName' onClick={updateName}>updateName</div>
</div>
)
}
}
test('connects to react components', () => {
const store = getStore()
const dynamicLogic = kea({
key: (props) => props.id,
path: (key) => ['scenes', 'something', key],
actions: ({ constants }) => ({
updateName: name => ({ name })
}),
reducers: ({ actions, constants }) => ({
name: ['chirpy', PropTypes.string, {
[actions.updateName]: (state, payload) => payload.name + payload.key
}]
}),
selectors: ({ constants, selectors }) => ({
capitalizedName: [
() => [selectors.name],
(name) => {
return name.trim().split(' ').map(k => `${k.charAt(0).toUpperCase()}${k.slice(1).toLowerCase()}`).join(' ')
},
PropTypes.string
]
})
})
const ConnectedComponent = dynamicLogic(SampleComponent)
const wrapper = mount(
<Provider store={store}>
<ConnectedComponent id={12} />
</Provider>
)
expect(wrapper.find('.id').text()).toEqual('12')
expect(wrapper.find('.name').text()).toEqual('chirpy')
expect(wrapper.find('.capitalizedName').text()).toEqual('Chirpy')
// for now we must dispatch a discard action to regenerate the store
store.dispatch({ type: 'discard' })
expect(store.getState()).toEqual({ scenes: { something: { 12: { name: 'chirpy' } } } })
const sampleComponent = wrapper.find('SampleComponent').node
expect(sampleComponent.actions).toBeDefined()
expect(Object.keys(sampleComponent.actions)).toEqual(['updateName'])
const { updateName } = sampleComponent.actions
updateName('somename')
expect(store.getState()).toEqual({ scenes: { something: { 12: { name: 'somename12' } } } })
wrapper.render()
expect(wrapper.find('.id').text()).toEqual('12')
expect(wrapper.find('.name').text()).toEqual('somename12')
expect(wrapper.find('.capitalizedName').text()).toEqual('Somename12')
})
Other resources
Check out the source documentation on testing redux, selectors and sagas.