【问题标题】:Cannot figure out how to test redux-saga function with redux-saga-test-plan无法弄清楚如何使用 redux-saga-test-plan 测试 redux-saga 功能
【发布时间】:2017-04-07 19:50:21
【问题描述】:

我正在自学如何使用redux-saga,同时自学单元测试,特别是 Jest。我从 redux-saga 的文档中获取了一个示例 saga,在这里:

http://yelouafi.github.io/redux-saga/docs/advanced/NonBlockingCalls.html

...并出于我自己的目的对其进行了修改。它应该是一个简单的身份验证处理程序,它侦听登录或注销操作(因为该函数不知道用户是否已登录),然后采取适当的行动。我已经在应用程序中测试了该功能,它似乎按预期运行,这很棒。函数如下:

function* authFlow() { 
    while (true) {
        const initialAction = yield take (['LOGIN', 'LOGOUT']);
        if (initialAction.type === 'LOGIN') {
            const { username, password } = initialAction.payload;
            const authTask = yield fork(
                authorizeWithRemoteServer, 
                { username: username, password: password }
            );
            const action = yield take(['LOGOUT', 'LOGIN_FAIL']);
            if (action.type === 'LOGOUT') {
                yield cancel(authTask);
                yield call (unauthorizeWithRemoteServer)
            }
        } else {
            yield call (unauthorizeWithRemoteServer)
        }
    }
}

这看起来相当简单,但我很难测试它。以下是我的基于 Jest 的测试脚本的注释版本:

it ('authFlow() should work with successful login and then successful logout', () => {
  const mockCredentials = { 
      username: 'User', 
      password: 'goodpassword' 
  };
  testSaga( stateAuth.watcher )
    // This should test the first 'yield', which is 
    // waiting for LOGIN or LOGOUT. It works
    .next()
    .take(['LOGIN', 'LOGOUT'])

    // This should test 'authorizeWithRemoteServer', 
    // and appears to do that properly
    .next({ 
        type: 'LOGIN', 
        payload: mockCredentials 
    })
    .fork( 
        stateAuth.authorizeWithRemoteServer, 
        mockCredentials)

    // This should reflect 'yield take' after the 'yield fork', 
    // and does so
    .next()
    .take(['LOGOUT', 'LOGIN_FAIL'])

    /* 
       This is where I don't understand what's happening. 
       What I would think I should do is something like this, 
       if I want to test the logout path:
       .next({ type: 'LOGOUT' })
       .cancel(createMockTask())

       ...but that results in the following, perhaps predictable, error:

       cancel(task): argument task is undefined

       What I found does make the test not fail is the following line, but 
       I do not understand why it works. The fact that it matches 
       "take(['LOGIN', 'LOGOUT'])" indicates that it has 
       looped back to the top of the generator
    */
    .next(createMockTask())
    .take(['LOGIN', 'LOGOUT'])
})

所以要么我做错了 sagas,要么我不明白如何测试 sagas,或者测试这种 sagas 真的很难而且可能不切实际。

那么这里发生了什么?提前致谢!

【问题讨论】:

    标签: unit-testing redux jestjs redux-saga redux-saga-test-plan


    【解决方案1】:

    不知道答案是否仍然与您相关,但以防万一其他人将来偶然发现此问题:

    排队

    .next().take(['LOGOUT', 'LOGIN_FAIL'])
    

    你基本上是通过undefined,这意味着这一行的产量:

    const action = yield take(['LOGOUT', 'LOGIN_FAIL']);
    

    使action 成为undefined

    你应该做的是在该行传递模拟任务:

    .next(createMockTask()).take(['LOGOUT', 'LOGIN_FAIL'])
    

    我认为这将是正确的测试

    it ('authFlow() should work with successful login and then successful logout', () => {
      const mockCredentials = {username: 'User', password: 'goodpassword'};
      testSaga( stateAuth.watcher )
        //this should test the first 'yield', which is waiting for LOGIN or LOGOUT. It works
        .next().take(['LOGIN', 'LOGOUT'])
    
        // This should test 'authorizeWithRemoteServer', and appears to do that properly
        .next({type: 'LOGIN', payload: mockCredentials}).fork( stateAuth.authorizeWithRemoteServer, mockCredentials)
    
        // We pass a mock task here
        .next(createMockTask()).take(['LOGOUT', 'LOGIN_FAIL'])
    
        // And then this should be correct
        .next({type: 'LOGOUT'}).cancel(createMockTask())
    
        // after which the saga loops back
        .take(['LOGIN', 'LOGOUT'])
    })
    

    请记住,当调用next() 时,您正在履行之前的收益。

    更新:哎呀,createMockTask() 的结果应该被存储以便能够将其用于断言。这应该是正确的代码:

    it ('authFlow() should work with successful login and then successful logout', () => {
      const mockCredentials = {username: 'User', password: 'goodpassword'};
      const mockTask = createMockTask();
      testSaga( stateAuth.watcher )
        //this should test the first 'yield', which is waiting for LOGIN or LOGOUT. It works
        .next().take(['LOGIN', 'LOGOUT'])
    
        // This should test 'authorizeWithRemoteServer', and appears to do that properly
        .next({type: 'LOGIN', payload: mockCredentials}).fork( stateAuth.authorizeWithRemoteServer, mockCredentials)
    
        // We pass a mock task here
        .next(mockTask).take(['LOGOUT', 'LOGIN_FAIL'])
    
        // And then this should be correct
        .next({type: 'LOGOUT'}).cancel(mockTask)
    
        // after which the saga loops back
        .take(['LOGIN', 'LOGOUT'])
    })
    

    【讨论】:

    • 我尝试了你的建议(谢谢!),它让我更接近,但现在在线.next({type: actions.LOGOUT}).cancel(createMockTask()),我得到这个:断言4失败:取消效果不匹配,但是“预期" 和 "实际" 值匹配。我将在下一条评论中发布实际与预期的内容。
    • 预期:{ '@@redux-saga/TASK': true, isRunning: [Function: isRunning],result: [Function: result], error: [Function: error], setRunning: [ Function: setRunning], setResult: [Function: setResult], setError: [Function: setError] } 实际: { '@@redux-saga/TASK': true, isRunning: [Function: isRunning],result: [Function: result ],错误:[函数:错误],setRunning:[函数:setRunning],setResult:[函数:setResult],setError:[函数:setError]}
    • 啊是的,我犯了一个错误,请参阅更新代码的答案。
    • 啊!我想我没有意识到的一件事是,当您调用 createMockTask() 时,该实例具有唯一签名,因此我需要执行 const mockTask1 = createMockTask() 之类的操作,然后多次引用它,就像您在示例中所做的那样.我慢慢开始明白这一点。再次感谢。
    • 是的,这正是问题所在。无论如何,很乐意提供帮助!
    猜你喜欢
    • 2022-01-08
    • 1970-01-01
    • 2018-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-06
    • 2018-06-10
    • 1970-01-01
    相关资源
    最近更新 更多