无法弄清楚如何使用 redux-saga-test-plan 测试 redux-saga 功能

Posted

技术标签:

【中文标题】无法弄清楚如何使用 redux-saga-test-plan 测试 redux-saga 功能【英文标题】:Cannot figure out how to test redux-saga function with redux-saga-test-plan 【发布时间】: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 真的很难而且可能不切实际。

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

【问题讨论】:

【参考方案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() 之类的操作,然后多次引用它,就像您在示例中所做的那样.我慢慢开始明白这一点。再次感谢。 是的,这正是问题所在。无论如何,很乐意提供帮助!

以上是关于无法弄清楚如何使用 redux-saga-test-plan 测试 redux-saga 功能的主要内容,如果未能解决你的问题,请参考以下文章

无法弄清楚如何修复错误

无法弄清楚如何使用 NSTimer 更改视图

不推荐使用 each() 函数;无法弄清楚如何更改为 foreach [重复]

无法弄清楚如何通过仅使用类的函数删除整个链接列表

使用 swift 我无法弄清楚视频停止后如何进入新屏幕

无法弄清楚如何使用适用于 Linux 和 composer 的 Windows 子系统运行 box\Spout