fxmgr
Version:
fixture-setup helper, designed mainly for bdd test-runners, (but not exclusively)
126 lines (106 loc) • 3.55 kB
JavaScript
const inst = {}
module.exports = {
init,
get,
of: get,
rm: name => { delete inst[name] },
}
function init({
toFixture = require('./fixture'),
load = require('require-yml'),
name = 'default',
loadFixtures,
//the essense:
fixtures = loadFixtures && load(loadFixtures),
stores: storeConfigs = {},
}) {
if (inst[name]) throw contextNameTaken(name)
//TBD: validate stores and fixtures
const stores = initStores(storeConfigs)
const { fx, storesData } = processFixtures(fixtures, stores)
const storeHooks = gatherStoreHooks(stores, storesData)
return inst[name] = {
stores,
fx,
fixtures: fx,
seed: () => eachStore(storeHooks, 'seed'),
beforeAll: () => eachStore(storeHooks, 'beforeAll'),
afterAll: () => eachStore(storeHooks, 'afterAll'),
}
function initStores(storeConfigs) {
return Object.entries(storeConfigs).reduce(
(stores, [name, { type, ...options }]) => {
//TBD: support 3rd party types from node-modules
const storeFactory = require(`./test-db-${type}`)
const store = storeFactory(options)
delete options.config
store[Symbol.for('options')] = options
stores[name] = store
return stores
},
{},
)
}
function processFixtures(fixtures, stores) {
return Object.entries(fixtures).reduce(
({ fx, storesData }, [entityName, fixture]) => {
if (!fixture.entity) fixture.entity = entityName
fx[entityName] = toFixture(fixture, stores)
fixture.stores && Object.entries(fixture.stores).forEach(
([store, { seed, setup, teardown }]) => storesData[store][entityName] = { seed, setup, teardown },
)
return { fx, storesData }
},
{
fx: {},
storesData: Object.keys(stores).reduce((d, store) => ({ ...d, [store]: {} }), {}),
},
)
}
function gatherStoreHooks(stores, storesData) {
return Object.entries(storesData).map(([name, data]) => stores[name].useData(data))
}
function eachStore(storeHooks, step) {
const errors = []
return Promise
.all(storeHooks.map(
store => store[step]().catch(e => errors.push(e) && null),
))
.then(
res => errors.length
? Promise.reject(Object.assign(new Error(`${step}: one or more stores ended with errors`), {
step,
errors: errors.map(e => ({ message: e.message, ...e })),
actual: errors.map(e => ({ message: e.message, ...e })),
expected: [],
}))
: res,
)
}
}
function get(name = 'default') {
const mgr = inst[name]
if (mgr) return mgr
throw Object.assign(new Error(`no such fxmgr instance: ${name}`), {
description: [
'This error is thrown when trying to get a fxmgr by name, but there is',
'no initiated fxmgr under this name',
'make sure that in your order of operations the context is first',
'initiated, and only then accessed',
],
code: 'UNINITIATED',
uninitiaedName: name,
})
}
function contextNameTaken(occupiedName) {
return Object.assign(new Error('fxmgr instance name conflict'), {
description: [
'This error is thrown when a fxmgr is initiated under a name that is already initiated.',
'You may initiate as mamny Fixture-Managers as you need, however their names must be unique.',
'If you did not provide a name - the default name is: \'default\'',
'Use `fxmgr.of(name) to retrieve an existing one',
],
code: 'NAME_CONFLICT',
occupiedName,
})
}