如何对使用redux-thunk的redux应用程序进行单元测试

时间:2017-03-08 17:52:16

标签: unit-testing redux redux-thunk

这是我在Mocha / Chai中编写的测试存根。我可以轻松地调度一个动作并断言状态等于我的期望,但是如何验证它是否遵循了预期的过程(IE早期的测试)?

/**
* This test describes the INITIALIZE_STATE action.
* The action is asynchronous using the async/await pattern to query
* The database. The action creator returns a thunk which should in turn
* return the new state with a list of tables and their relationships with eachother
**/

describe('Initialize state', () => {
    it('Should check if state is empty', () => {});
    it('Should check if tables/relationships exist', () => {});
    it('Should check if new tables have been added', () => {});
    it('Should merge new and existing tables and relationships', () => {
        // Here is where we would dispatch the INITIALIZE_STATE 
       // action and assert that the new state is what I expect it to be.
    });
});

我还没有为实际操作本身编写任何代码,因为我希望代码能够通过这些验证。一些伪代码可能看起来像这样

export function initializeState() {
    return function(dispatch) {
        let empty = store.getState().empty
        let state = (empty) ? await getLastPersistedState() : store.getState()
        let tables = state.tables;
        let payload = tables.concat(await getNewTables(tables));
        dispatch({type: 'INITIALIZE_STATE', payload});
    }
}

function getLastPerisistedState() {
    return mongodb.findall(state, (s) => s);
}

function getNewTables(tableFilter) {
    return sql.query("select table_name from tables where table_name not in (" + tableFilter + ")");
} 

1 个答案:

答案 0 :(得分:0)

这是我提出的解决方案。可能有一个更好的,但到目前为止还没有人能够提供一个。我决定采用重构的一系列操作和一个单独的商店进行测试。这些动作是函数生成器而不是使用thunk。它们会产生thunk将在生产代码中分派的操作。在我的测试中,我可以自己发送这些动作,并验证结果状态是我所期望的。这正是thunk将要做的事情,但它允许我插入自己作为中间人,而不是依赖于thunk中间件。

这也非常有用,因为它使得动作逻辑与调度和状态逻辑分离非常容易,即使在测试异步流时也是如此。

对于数据库,我自动生成存根并使用promises来模拟异步查询。由于这个项目无论如何都在使用sequelize,我只是使用sequelize来生成存根。

这是代码

<强> _actions.js

export function *initializeState() {
    //We will yield the current step so that we can test it step by step

    //Should check if state is empty
    yield {type: 'UPDATE_STEP', payload: 'IS_STATE_EMPTY'};
    yield {type: 'UPDATE_STEP_RESULT', payload: stateIsEmpty()};

    if(stateIsEmpty()) {
        //todo: Implement branch logic if state is empty
    }
    //...
}

<强> sequelize / _test / generate.js

async function createMockFromSql(db, sql, filename) {
    let results = await db.query(sql, {type: db.Sequelize.QueryTypes.SELECT});
    return new Promise((resolve, reject) => {

        // Trim the results to a reasonable size. Keep it unique each time
        // for more rigorous testing

        console.log('trimming result set');
        while (results.length > 50) {
            results.splice(results.length * Math.random() | 0, 1);
        }

        fs.writeFile(path.resolve(__dirname, '../../sequelize/_test', filename), JSON.stringify(results, null, 2), err => {
            if (err) {
                console.error(err);
                reject(false);
            }

            resolve(true);
        })
    })
}

<强>测试/ actions.js

...
it('Should check if state is empty', () => {
    let action = initializeState();
    expect(action.next()).to.deep.equal({type: 'UPDATE_STEP', payload: 'IS_STATE_EMPTY'})
});