如何用玩笑测试 redux saga?
How to test redux saga with jest?
React 中的新成员,react-redux/saga 和 jest
考虑:
-----组件()----
componentDidMount() {
this.props.actions.initTodos(
axios,
ajaxURLConstants.WP_GET_TODOS,
appStateActions.setAppInIdle,
appStateActions.setAppInProcessing,
todosActions.todosInitialized
);
}
所以当我的 TodoApp 组件挂载时,它会调度 INIT_TODOS 动作,然后 我的 root saga 正在监听 ,当它捕获到它时,将产生相应的 worker saga 以采取相应的行动。
-----相应的工人传奇-----
export function* initTodosSaga( action ) {
try {
yield put( action.setAppInProcessing() );
let response = yield call( action.axios.get , action.WP_GET_TODOS );
if ( response.data.status === "success" )
yield put( action.todosInitialized( response.data.todos ) );
else {
console.log( response );
alert( response.data.error_msg );
}
} catch ( error ) {
console.log( "error" , error );
alert( "Failed to load initial data" );
}
yield put( action.setAppInIdle() );
}
-----目前的测试-----
import todos from "../../__fixtures__/todos";
import { initTodosSaga } from "../todosSaga";
test( "saga test" , () => {
let response = {
status : "success",
todos
},
action = {
axios : {
get : function() {
return new Promise( ( resolve , reject ) => {
resolve( response );
} );
}
},
WP_GET_TODOS : "dummy url",
setAppInIdle : jest.fn(),
setAppInProcessing : jest.fn(),
todosInitialized : jest.fn()
};
let initTodosSagaGen = initTodosSaga( action );
initTodosSagaGen.next();
expect( action.setAppInIdle ).toHaveBeenCalled();
} );
-----测试结果-----
所以重要的部分是这个
console.error node_modules\redux-saga\lib\internal\utils.js:240
未在检查 put(action) 时捕获:参数操作未定义
但我有 console.log 我在 worker saga 中通过测试的操作,实际上它不是未定义的
我错过了什么?
提前致谢。
------------更新------------
好的,请注意顶部,它正在抱怨这行代码
yield put( action.setAppInIdle() );
它在 try catch 块之外,所以我做了一些更改
1.) 我将上面的代码移到 try catch 块中,就在
的 else 语句之后
if ( response.data.status === "success" )
请检查上面的 initTodosSaga 代码
然后在我的 saga 测试中,我测试
expect( action.setAppInProcessing ).toHaveBeenCalled();
而不是 setAppInIdle 间谍功能
这是测试结果
所以测试通过了! 但它仍然抱怨操作未定义
现在有趣的是如果在我的传奇测试中,如果我现在测试这个
expect( action.setAppInProcessing ).toHaveBeenCalled();
expect( action.setAppInIdle ).toHaveBeenCalled();
这是结果
所以现在它仍然抱怨操作仍然未定义(我没有包括在我的屏幕截图中,但仍然与上面相同)
加上我关于 setAppInIdle 间谍函数的第二个断言没有被调用,但是 setAppInProcessing 确实通过了!
我希望这些附加信息有助于解决这个问题。
似乎在没有任何外部库帮助的情况下很难测试 redux saga
我用过
https://github.com/jfairbank/redux-saga-test-plan
这个库很好。
现在是我的测试
--------------------测试 1-------------------- ---
所以对于这个测试,我传递了 action payload saga 运行所需的几乎所有内容,例如。 axios ,动作创建者功能等...
更像是遵循依赖注入的原则,所以很容易测试。
-----TodoApp组件-----
componentDidMount() {
this.props.actions.initTodos(
axios,
ajaxURLConstants.WP_GET_TODOS,
appStateActions.setAppInIdle,
appStateActions.setAppInProcessing,
todosActions.todosInitialized,
todosActions.todosFailedInit
);
}
因此,当组件确实挂载时,它会触发一个动作,我的 root saga 会监听并捕获该动作,然后生成相应的 worker saga 以采取相应的行动
再次注意我传递了 worker saga 在动作有效载荷上正常运行所需的所有必要数据。
-----initTodoSaga (Worker Saga)-----
export function* initTodosSaga( action ) {
try {
yield put( action.setAppInProcessing() );
let response = yield call( action.axios.get , action.WP_GET_TODOS );
if ( response.data.status === "success" )
yield put( action.todosInitialized( response.data.todos ) );
else {
console.log( response );
alert( response.data.error_msg );
yield put( action.todosFailedInit( response ) );
}
} catch ( error ) {
console.log( "error" , error );
alert( "Failed to load initial data" );
yield put( action.todosFailedInit( error ) );
}
yield put( action.setAppInIdle() );
}
-----传奇测试-----
import { expectSaga } from "redux-saga-test-plan";
import { initTodosSaga } from "../todosSaga";
test( "should initialize the ToDos state via the initTodoSaga" , () => {
let response = {
data : {
status : "success",
todos
}
},
action = {
axios : {
get : function() {
return new Promise( ( resolve , reject ) => {
resolve( response );
} );
}
},
WP_GET_TODOS : "dummy url",
setAppInIdle : appStateActions.setAppInIdle,
setAppInProcessing : appStateActions.setAppInProcessing,
todosInitialized : todosStateActions.todosInitialized,
todosFailedInit : todosStateActions.todosFailedInit
};
// This is the important bit
// These are the assertions
// Basically saying that the actions below inside the put should be dispatched when this saga is executed
return expectSaga( initTodosSaga , action )
.put( appStateActions.setAppInProcessing() )
.put( todosStateActions.todosInitialized( todos ) )
.put( appStateActions.setAppInIdle() )
.run();
} );
我的测试通过了耶! :)
现在向您展示测试失败时的错误消息,我将在 initTodosSaga
中注释掉这行代码
yield put( action.setAppInIdle() );
所以现在断言
.put( appStateActions.setAppInIdle() )
现在应该会失败
所以它输出 put expectation unmet 这是有道理的,因为我们期望被解雇的动作没有
--------------------测试2-------------------- --
现在这个测试是针对一个 saga 的,它导入了一些它需要操作的东西,不像我的第一次测试,我在动作有效负载中提供 axios,动作创建者
这个 saga 导入了 axios,它需要操作的 action creators
值得庆幸的是 Redux Saga 测试计划 有一些辅助函数 "feed" 虚拟数据到 saga
我将跳过触发 root saga 正在监听的动作的组件,它不重要,我将直接粘贴 saga 和 saga 测试
----addTodoSaga----
/** global ajaxurl */
import axios from "axios";
import { call , put } from "redux-saga/effects";
import * as appStateActions from "../actions/appStateActions";
import * as todosActions from "../actions/todosActions";
export function* addTodoSaga( action ) {
try {
yield put( appStateActions.setAppInProcessing() );
let formData = new FormData;
formData.append( "todo" , JSON.stringify( action.todo ) );
let response = yield call( axios.post , ajaxurl + "?action=wptd_add_todo" , formData );
if ( response.data.status === "success" ) {
yield put( todosActions.todoAdded( action.todo ) );
action.successCallback();
} else {
console.log( response );
alert( response.data.error_msg );
}
} catch ( error ) {
console.log( error );
alert( "Failed to add new todo" );
}
yield put( appStateActions.setAppInIdle() );
}
-----测试-----
import axios from "axios";
import { expectSaga } from "redux-saga-test-plan";
import * as matchers from "redux-saga-test-plan/matchers";
import * as appStateActions from "../../actions/appStateActions";
import * as todosStateActions from "../../actions/todosActions";
import { addTodoSaga } from "../todosSaga";
test( "should dispatch TODO_ADDED action when adding new todo is successful" , () => {
let response = {
data : { status : "success" }
},
todo = {
id : 1,
completed : false,
title : "Browse 9gag tonight"
},
action = {
todo,
successCallback : jest.fn()
};
// Here are the assertions
return expectSaga( addTodoSaga , action )
.provide( [
[ matchers.call.fn( axios.post ) , response ]
] )
.put( appStateActions.setAppInProcessing() )
.put( todosStateActions.todoAdded( todo ) )
.put( appStateActions.setAppInIdle() )
.run();
} );
因此提供函数允许您模拟函数调用,同时提供它应该 return
的虚拟数据
就是这样,我现在可以测试我的传奇了!耶!
还有一件事,当我 运行 我的 saga 测试导致执行带有警报代码的代码时
例如
alert( "Earth is not flat!" );
我在控制台上得到了这个
Error: Not implemented: window.alert
下面还有一堆堆栈跟踪,所以可能是因为警报对象不在节点上?我该如何隐藏这个?如果你们有答案,请添加评论。
希望对大家有所帮助
这是您的测试的工作版本:
import todos from '../../__fixtures__/todos';
import { initTodosSaga } from '../todosSaga';
import { put, call } from 'redux-saga/effects';
test('saga test', () => {
const response = {
data: {
status: 'success',
todos
}
};
const action = {
axios: {
get() {}
},
WP_GET_TODOS: 'dummy url',
setAppInIdle: jest.fn().mockReturnValue({ type: 'setAppInIdle' }),
setAppInProcessing: jest.fn().mockReturnValue({ type: 'setAppInProcessing' }),
todosInitialized: jest.fn().mockReturnValue({ type: 'todosInitialized' })
};
let result;
const initTodosSagaGen = initTodosSaga(action);
result = initTodosSagaGen.next();
expect(result.value).toEqual(put(action.setAppInProcessing()));
expect(action.setAppInProcessing).toHaveBeenCalled();
result = initTodosSagaGen.next();
expect(result.value).toEqual(call(action.axios.get, action.WP_GET_TODOS));
result = initTodosSagaGen.next(response);
expect(action.todosInitialized).toHaveBeenCalled();
expect(result.value).toEqual(put(action.todosInitialized(response.data.todos)));
result = initTodosSagaGen.next();
expect(action.setAppInIdle).toHaveBeenCalled();
expect(result.value).toEqual(put(action.setAppInIdle()));
});
一些注意事项:
- 你实际上不必让模拟 Axios.get return 任何东西
- 使用
expect
语句,我将生成器的产量与我期望生成器执行的操作(即执行 put
和 call
语句)进行比较
- 您的模拟回复中缺少
data
属性
React 中的新成员,react-redux/saga 和 jest
考虑:
-----组件()----
componentDidMount() {
this.props.actions.initTodos(
axios,
ajaxURLConstants.WP_GET_TODOS,
appStateActions.setAppInIdle,
appStateActions.setAppInProcessing,
todosActions.todosInitialized
);
}
所以当我的 TodoApp 组件挂载时,它会调度 INIT_TODOS 动作,然后 我的 root saga 正在监听 ,当它捕获到它时,将产生相应的 worker saga 以采取相应的行动。
-----相应的工人传奇-----
export function* initTodosSaga( action ) {
try {
yield put( action.setAppInProcessing() );
let response = yield call( action.axios.get , action.WP_GET_TODOS );
if ( response.data.status === "success" )
yield put( action.todosInitialized( response.data.todos ) );
else {
console.log( response );
alert( response.data.error_msg );
}
} catch ( error ) {
console.log( "error" , error );
alert( "Failed to load initial data" );
}
yield put( action.setAppInIdle() );
}
-----目前的测试-----
import todos from "../../__fixtures__/todos";
import { initTodosSaga } from "../todosSaga";
test( "saga test" , () => {
let response = {
status : "success",
todos
},
action = {
axios : {
get : function() {
return new Promise( ( resolve , reject ) => {
resolve( response );
} );
}
},
WP_GET_TODOS : "dummy url",
setAppInIdle : jest.fn(),
setAppInProcessing : jest.fn(),
todosInitialized : jest.fn()
};
let initTodosSagaGen = initTodosSaga( action );
initTodosSagaGen.next();
expect( action.setAppInIdle ).toHaveBeenCalled();
} );
-----测试结果-----
所以重要的部分是这个
console.error node_modules\redux-saga\lib\internal\utils.js:240
未在检查 put(action) 时捕获:参数操作未定义
但我有 console.log 我在 worker saga 中通过测试的操作,实际上它不是未定义的
我错过了什么?
提前致谢。
------------更新------------
好的,请注意顶部,它正在抱怨这行代码
yield put( action.setAppInIdle() );
它在 try catch 块之外,所以我做了一些更改
1.) 我将上面的代码移到 try catch 块中,就在
的 else 语句之后if ( response.data.status === "success" )
请检查上面的 initTodosSaga 代码
然后在我的 saga 测试中,我测试
expect( action.setAppInProcessing ).toHaveBeenCalled();
而不是 setAppInIdle 间谍功能
这是测试结果
所以测试通过了! 但它仍然抱怨操作未定义
现在有趣的是如果在我的传奇测试中,如果我现在测试这个
expect( action.setAppInProcessing ).toHaveBeenCalled();
expect( action.setAppInIdle ).toHaveBeenCalled();
这是结果
所以现在它仍然抱怨操作仍然未定义(我没有包括在我的屏幕截图中,但仍然与上面相同)
加上我关于 setAppInIdle 间谍函数的第二个断言没有被调用,但是 setAppInProcessing 确实通过了!
我希望这些附加信息有助于解决这个问题。
似乎在没有任何外部库帮助的情况下很难测试 redux saga
我用过 https://github.com/jfairbank/redux-saga-test-plan
这个库很好。
现在是我的测试
--------------------测试 1-------------------- ---
所以对于这个测试,我传递了 action payload saga 运行所需的几乎所有内容,例如。 axios ,动作创建者功能等... 更像是遵循依赖注入的原则,所以很容易测试。
-----TodoApp组件-----
componentDidMount() {
this.props.actions.initTodos(
axios,
ajaxURLConstants.WP_GET_TODOS,
appStateActions.setAppInIdle,
appStateActions.setAppInProcessing,
todosActions.todosInitialized,
todosActions.todosFailedInit
);
}
因此,当组件确实挂载时,它会触发一个动作,我的 root saga 会监听并捕获该动作,然后生成相应的 worker saga 以采取相应的行动
再次注意我传递了 worker saga 在动作有效载荷上正常运行所需的所有必要数据。
-----initTodoSaga (Worker Saga)-----
export function* initTodosSaga( action ) {
try {
yield put( action.setAppInProcessing() );
let response = yield call( action.axios.get , action.WP_GET_TODOS );
if ( response.data.status === "success" )
yield put( action.todosInitialized( response.data.todos ) );
else {
console.log( response );
alert( response.data.error_msg );
yield put( action.todosFailedInit( response ) );
}
} catch ( error ) {
console.log( "error" , error );
alert( "Failed to load initial data" );
yield put( action.todosFailedInit( error ) );
}
yield put( action.setAppInIdle() );
}
-----传奇测试-----
import { expectSaga } from "redux-saga-test-plan";
import { initTodosSaga } from "../todosSaga";
test( "should initialize the ToDos state via the initTodoSaga" , () => {
let response = {
data : {
status : "success",
todos
}
},
action = {
axios : {
get : function() {
return new Promise( ( resolve , reject ) => {
resolve( response );
} );
}
},
WP_GET_TODOS : "dummy url",
setAppInIdle : appStateActions.setAppInIdle,
setAppInProcessing : appStateActions.setAppInProcessing,
todosInitialized : todosStateActions.todosInitialized,
todosFailedInit : todosStateActions.todosFailedInit
};
// This is the important bit
// These are the assertions
// Basically saying that the actions below inside the put should be dispatched when this saga is executed
return expectSaga( initTodosSaga , action )
.put( appStateActions.setAppInProcessing() )
.put( todosStateActions.todosInitialized( todos ) )
.put( appStateActions.setAppInIdle() )
.run();
} );
我的测试通过了耶! :) 现在向您展示测试失败时的错误消息,我将在 initTodosSaga
中注释掉这行代码yield put( action.setAppInIdle() );
所以现在断言
.put( appStateActions.setAppInIdle() )
现在应该会失败
所以它输出 put expectation unmet 这是有道理的,因为我们期望被解雇的动作没有
--------------------测试2-------------------- --
现在这个测试是针对一个 saga 的,它导入了一些它需要操作的东西,不像我的第一次测试,我在动作有效负载中提供 axios,动作创建者
这个 saga 导入了 axios,它需要操作的 action creators
值得庆幸的是 Redux Saga 测试计划 有一些辅助函数 "feed" 虚拟数据到 saga
我将跳过触发 root saga 正在监听的动作的组件,它不重要,我将直接粘贴 saga 和 saga 测试
----addTodoSaga----
/** global ajaxurl */
import axios from "axios";
import { call , put } from "redux-saga/effects";
import * as appStateActions from "../actions/appStateActions";
import * as todosActions from "../actions/todosActions";
export function* addTodoSaga( action ) {
try {
yield put( appStateActions.setAppInProcessing() );
let formData = new FormData;
formData.append( "todo" , JSON.stringify( action.todo ) );
let response = yield call( axios.post , ajaxurl + "?action=wptd_add_todo" , formData );
if ( response.data.status === "success" ) {
yield put( todosActions.todoAdded( action.todo ) );
action.successCallback();
} else {
console.log( response );
alert( response.data.error_msg );
}
} catch ( error ) {
console.log( error );
alert( "Failed to add new todo" );
}
yield put( appStateActions.setAppInIdle() );
}
-----测试-----
import axios from "axios";
import { expectSaga } from "redux-saga-test-plan";
import * as matchers from "redux-saga-test-plan/matchers";
import * as appStateActions from "../../actions/appStateActions";
import * as todosStateActions from "../../actions/todosActions";
import { addTodoSaga } from "../todosSaga";
test( "should dispatch TODO_ADDED action when adding new todo is successful" , () => {
let response = {
data : { status : "success" }
},
todo = {
id : 1,
completed : false,
title : "Browse 9gag tonight"
},
action = {
todo,
successCallback : jest.fn()
};
// Here are the assertions
return expectSaga( addTodoSaga , action )
.provide( [
[ matchers.call.fn( axios.post ) , response ]
] )
.put( appStateActions.setAppInProcessing() )
.put( todosStateActions.todoAdded( todo ) )
.put( appStateActions.setAppInIdle() )
.run();
} );
因此提供函数允许您模拟函数调用,同时提供它应该 return
的虚拟数据就是这样,我现在可以测试我的传奇了!耶!
还有一件事,当我 运行 我的 saga 测试导致执行带有警报代码的代码时
例如
alert( "Earth is not flat!" );
我在控制台上得到了这个
Error: Not implemented: window.alert
下面还有一堆堆栈跟踪,所以可能是因为警报对象不在节点上?我该如何隐藏这个?如果你们有答案,请添加评论。
希望对大家有所帮助
这是您的测试的工作版本:
import todos from '../../__fixtures__/todos';
import { initTodosSaga } from '../todosSaga';
import { put, call } from 'redux-saga/effects';
test('saga test', () => {
const response = {
data: {
status: 'success',
todos
}
};
const action = {
axios: {
get() {}
},
WP_GET_TODOS: 'dummy url',
setAppInIdle: jest.fn().mockReturnValue({ type: 'setAppInIdle' }),
setAppInProcessing: jest.fn().mockReturnValue({ type: 'setAppInProcessing' }),
todosInitialized: jest.fn().mockReturnValue({ type: 'todosInitialized' })
};
let result;
const initTodosSagaGen = initTodosSaga(action);
result = initTodosSagaGen.next();
expect(result.value).toEqual(put(action.setAppInProcessing()));
expect(action.setAppInProcessing).toHaveBeenCalled();
result = initTodosSagaGen.next();
expect(result.value).toEqual(call(action.axios.get, action.WP_GET_TODOS));
result = initTodosSagaGen.next(response);
expect(action.todosInitialized).toHaveBeenCalled();
expect(result.value).toEqual(put(action.todosInitialized(response.data.todos)));
result = initTodosSagaGen.next();
expect(action.setAppInIdle).toHaveBeenCalled();
expect(result.value).toEqual(put(action.setAppInIdle()));
});
一些注意事项:
- 你实际上不必让模拟 Axios.get return 任何东西
- 使用
expect
语句,我将生成器的产量与我期望生成器执行的操作(即执行put
和call
语句)进行比较 - 您的模拟回复中缺少
data
属性