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

hai*_*rbo 4 unit-testing jestjs redux redux-saga redux-saga-test-plan

我在教自己如何使用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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它似乎相当简单,但我很难测试它.以下是基于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'])
})
Run Code Online (Sandbox Code Playgroud)

所以要么我做错了传闻,要么我不懂如何测试传奇,或者测试这种传奇真的很难,也许是不切实际的.

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

Vle*_*ert 6

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

在线

.next().take(['LOGOUT', 'LOGIN_FAIL'])
Run Code Online (Sandbox Code Playgroud)

你基本上是在路过undefined,这意味着这一行的收益率:

const action = yield take(['LOGOUT', 'LOGIN_FAIL']);
Run Code Online (Sandbox Code Playgroud)

原因actionundefined.

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

.next(createMockTask()).take(['LOGOUT', 'LOGIN_FAIL'])
Run Code Online (Sandbox Code Playgroud)

我认为这将是正确的测试

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'])
})
Run Code Online (Sandbox Code Playgroud)

请记住,在通话时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'])
})
Run Code Online (Sandbox Code Playgroud)