集成测试 redux-observable 发出多个 ajax 具有去抖动的请求
Integration test redux-observable that makes multiple ajax requests with debounce
我有一个 redux-observable 史诗轮询端点,获取进度更新,直到进度为 100%。轮询间隔是使用 debounceTime 实现的,如下所示:
function myEpic(action$, store, dependencies) {
return action$.ofType('PROCESSING')
.do(action => console.log(`RECEIVED ACTION: ${JSON.stringify(action)}`))
.debounceTime(1000, dependencies.scheduler)
.mergeMap(action => (
dependencies.ajax({ url: action.checkUrl })
.map((resp) => {
if (parseInt(resp.progress, 10) === 100) {
return { type: 'SUCCESS' };
}
return { checkUrl: resp.check_url, progress: resp.progress, type: 'PROCESSING' };
})));
}
这很好用,但我想编写一个集成测试来测试当进度为 25%、50%、100% 时商店的状态。
在我的集成测试中,我可以将 dependencies.scheduler
设置为 new VirtualTimeScheduler()
。
这就是我目前正在尝试的方式(使用玩笑):
describe('my integration test', () => {
const scheduler = new VirtualTimeScheduler();
beforeEach(() => {
// Fake ajax responses
const ajax = (request) => {
console.log(`FAKING REQUEST FOR URL: ${request.url}`);
if (request.url === '/check_url_1') {
return Observable.of({ progress: 25, check_url: '/check_url_2' });
} else if (request.url === '/check_url_2') {
return Observable.of({ progress: 50, check_url: '/check_url_3' });
} else if (request.url === '/check_url_3') {
return Observable.of({ progress: 100 });
}
return null;
};
store = configureStore(defaultState, { ajax, scheduler });
});
it('should update the store properly after each call', () => {
store.dispatch({ checkUrl: '/check_url_1', progress: 0, type: 'PROCESSING' });
scheduler.flush();
console.log('CHECK CORRECT STATE FOR PROGRESS 25');
scheduler.flush();
console.log('CHECK CORRECT STATE FOR PROGRESS 50');
scheduler.flush();
console.log('CHECK CORRECT STATE FOR PROGRESS 100');
});
});
我的预期输出是:
RECEIVED ACTION: {"checkUrl":"/check_url_1","progress":0,"type":"PROCESSING"}
FAKING REQUEST FOR URL: /check_url_1
CHECK CORRECT STATE FOR PROGRESS 25
RECEIVED ACTION: {"checkUrl":"/check_url_2","progress":25,"type":"PROCESSING"}
FAKING REQUEST FOR URL: /check_url_2
CHECK CORRECT STATE FOR PROGRESS 50
RECEIVED ACTION: {"checkUrl":"/check_url_3","progress":50,"type":"PROCESSING"}
# CHECK CORRECT STATE FOR PROGRESS 100
但我得到的输出是
RECEIVED ACTION: {"checkUrl":"/check_url_1","progress":0,"type":"PROCESSING","errors":null}
FAKING REQUEST FOR URL: /check_url_1
RECEIVED ACTION: {"checkUrl":"/check_url_2","progress":25,"type":"PROCESSING","errors":null}
CHECK CORRECT STATE FOR PROGRESS 25%
CHECK CORRECT STATE FOR PROGRESS 50%
CHECK CORRECT STATE FOR PROGRESS 100%
此时测试结束。我正在配置商店,以便我可以像推荐的那样模拟 ajax 请求和用于 debounceTime 的调度程序 here
所以我的问题是如何在三个 ajax 请求中的每一个请求之后测试我的商店的状态?
有趣的是,我试过你的代码并且相当有信心你刚刚在 debounceTime
运算符中发现了一个错误,它导致明显吞噬了预定的去抖动。坏消息是,即使该错误已修复,您的代码仍然无法按顺序执行您要查找的内容。
请耐心等待,因为狗屎即将成为现实:
- Epic 接收操作 PROCESSING 并安排去抖动,让执行执行您的测试
- 您的测试调用
scheduler.flush()
并且 VirtualScheduler 执行预定的去抖动工作,这会将原始的 PROCESSING 操作传递给 mergeMap
- 伪造ajax,同步发出响应
- 响应映射到第二个 PROCESSING 操作
- 您的史诗同步发出第二个动作
- 第二个动作由你的史诗递归接收并提供给去抖动
- debounceTime 运算符现在在 VirtualScheduler 上安排第二个动作,但是 debounceTime 运算符正在执行第一个动作中仍然的先前安排的工作。
- 调用堆栈展开一堆,直到它到达先前计划的去抖动工作中,从第一个动作开始,而第一个动作只是 next() 的第一个动作。
debounceTime
的 rxjs 代码然后设置 this.lastValue = null
and this.hasValue = false
这是 rxjs 错误,需要在进入目的地之前完成
- 堆栈进一步展开到 VirtualScheduler 的 运行ning flush() 方法,which now dequeues the second scheduled debounced action 因为它是同步添加的计划工作数组,在此之前刷新完成。请记住,到目前为止,我们只调用了
scheduler.flush()
一次,这是我们此时返回的函数。
- 第二个预定的去抖动工作是 运行,但是
this.hasValue === false
因为第一个预定的去抖动工作设置了它,所以 debounceTime 运算符不会发出任何东西。
- 堆栈展开到我们的第一个
scheduler.flush()
- 我们
console.log('CHECK CORRECT STATE FOR PROGRESS 25')
- 所有其他
scheduler.flush()
调用什么都不做,因为没有任何安排。
这在技术上是一个错误,但是没有人 运行 进入它并不奇怪,因为 运行 没有任何延迟地同步去抖动违背了它的要点, 除了当你在测试时,当然 。 This ticket is basically the same thing and OJ says RxJS doesn't make reentrancy guarantees, but I that might be up for debate in this case. I've filed a PR with the fix to discuss
请记住,这个错误不会解决您关于排序的基本问题,但会阻止操作被吞没。
我不知道如果您想保持 100% 的同步行为 (VirtualScheduler),您会如何做您想做的事情。在去抖动之间,您需要某种方法来屈服于您的测试。对我来说,当我编写集成测试时,如果有的话,我很少模拟。例如让去抖实际上自然地去抖,或者通过模拟 setTimeout 来更快地推进它们,但 仍然保持它们异步 这将返回到你的测试允许你检查状态,但也让你的测试异步。
任何想要复制的人,here's the StackBlitz code I used
答案是 re-write 异步测试。另外 note-worthy 是我必须通过返回 Observable.fromPromise
而不是常规的 Observable.of
来模拟 ajax 请求,否则它们仍然会被反跳吞没。这些方面的东西(使用笑话):
describe('my integration test', () => {
const scheduler = new VirtualTimeScheduler();
beforeEach(() => {
// Fake ajax responses
const ajax = request => (
Observable.fromPromise(new Promise((resolve) => {
if (request.url === '/check_url_1') {
resolve({ response: { progress: 25, check_url: '/check_url_2' } });
} else if (request.url === '/check_url_2') {
resolve({ response: { progress: 50, check_url: '/check_url_3' } });
} else {
resolve({ response: { progress: 100 } });
}
}))
);
store = configureStore(defaultState, { ajax, timerInterval: 1 });
});
it('should update the store properly after each call', (done) => {
let i = 0;
store.subscribe(() => {
switch (i) {
case 0:
console.log('CHECK CORRECT STATE FOR PROGRESS 0');
break;
case 1:
console.log('CHECK CORRECT STATE FOR PROGRESS 25');
break;
case 2:
console.log('CHECK CORRECT STATE FOR PROGRESS 50');
break;
case 3:
console.log('CHECK CORRECT STATE FOR PROGRESS 100');
done();
break;
default:
}
i += 1;
});
store.dispatch({ checkUrl: '/check_url_1', progress: 0, type: 'PROCESSING' });
});
});
我还通过将计时器间隔作为依赖项传递来将其设置为 1。在我的史诗中我是这样设置的:.debounceTime(dependencies.timerInterval || 1000)
我有一个 redux-observable 史诗轮询端点,获取进度更新,直到进度为 100%。轮询间隔是使用 debounceTime 实现的,如下所示:
function myEpic(action$, store, dependencies) {
return action$.ofType('PROCESSING')
.do(action => console.log(`RECEIVED ACTION: ${JSON.stringify(action)}`))
.debounceTime(1000, dependencies.scheduler)
.mergeMap(action => (
dependencies.ajax({ url: action.checkUrl })
.map((resp) => {
if (parseInt(resp.progress, 10) === 100) {
return { type: 'SUCCESS' };
}
return { checkUrl: resp.check_url, progress: resp.progress, type: 'PROCESSING' };
})));
}
这很好用,但我想编写一个集成测试来测试当进度为 25%、50%、100% 时商店的状态。
在我的集成测试中,我可以将 dependencies.scheduler
设置为 new VirtualTimeScheduler()
。
这就是我目前正在尝试的方式(使用玩笑):
describe('my integration test', () => {
const scheduler = new VirtualTimeScheduler();
beforeEach(() => {
// Fake ajax responses
const ajax = (request) => {
console.log(`FAKING REQUEST FOR URL: ${request.url}`);
if (request.url === '/check_url_1') {
return Observable.of({ progress: 25, check_url: '/check_url_2' });
} else if (request.url === '/check_url_2') {
return Observable.of({ progress: 50, check_url: '/check_url_3' });
} else if (request.url === '/check_url_3') {
return Observable.of({ progress: 100 });
}
return null;
};
store = configureStore(defaultState, { ajax, scheduler });
});
it('should update the store properly after each call', () => {
store.dispatch({ checkUrl: '/check_url_1', progress: 0, type: 'PROCESSING' });
scheduler.flush();
console.log('CHECK CORRECT STATE FOR PROGRESS 25');
scheduler.flush();
console.log('CHECK CORRECT STATE FOR PROGRESS 50');
scheduler.flush();
console.log('CHECK CORRECT STATE FOR PROGRESS 100');
});
});
我的预期输出是:
RECEIVED ACTION: {"checkUrl":"/check_url_1","progress":0,"type":"PROCESSING"}
FAKING REQUEST FOR URL: /check_url_1
CHECK CORRECT STATE FOR PROGRESS 25
RECEIVED ACTION: {"checkUrl":"/check_url_2","progress":25,"type":"PROCESSING"}
FAKING REQUEST FOR URL: /check_url_2
CHECK CORRECT STATE FOR PROGRESS 50
RECEIVED ACTION: {"checkUrl":"/check_url_3","progress":50,"type":"PROCESSING"}
# CHECK CORRECT STATE FOR PROGRESS 100
但我得到的输出是
RECEIVED ACTION: {"checkUrl":"/check_url_1","progress":0,"type":"PROCESSING","errors":null}
FAKING REQUEST FOR URL: /check_url_1
RECEIVED ACTION: {"checkUrl":"/check_url_2","progress":25,"type":"PROCESSING","errors":null}
CHECK CORRECT STATE FOR PROGRESS 25%
CHECK CORRECT STATE FOR PROGRESS 50%
CHECK CORRECT STATE FOR PROGRESS 100%
此时测试结束。我正在配置商店,以便我可以像推荐的那样模拟 ajax 请求和用于 debounceTime 的调度程序 here
所以我的问题是如何在三个 ajax 请求中的每一个请求之后测试我的商店的状态?
有趣的是,我试过你的代码并且相当有信心你刚刚在 debounceTime
运算符中发现了一个错误,它导致明显吞噬了预定的去抖动。坏消息是,即使该错误已修复,您的代码仍然无法按顺序执行您要查找的内容。
请耐心等待,因为狗屎即将成为现实:
- Epic 接收操作 PROCESSING 并安排去抖动,让执行执行您的测试
- 您的测试调用
scheduler.flush()
并且 VirtualScheduler 执行预定的去抖动工作,这会将原始的 PROCESSING 操作传递给mergeMap
- 伪造ajax,同步发出响应
- 响应映射到第二个 PROCESSING 操作
- 您的史诗同步发出第二个动作
- 第二个动作由你的史诗递归接收并提供给去抖动
- debounceTime 运算符现在在 VirtualScheduler 上安排第二个动作,但是 debounceTime 运算符正在执行第一个动作中仍然的先前安排的工作。
- 调用堆栈展开一堆,直到它到达先前计划的去抖动工作中,从第一个动作开始,而第一个动作只是 next() 的第一个动作。
debounceTime
的 rxjs 代码然后设置this.lastValue = null
andthis.hasValue = false
这是 rxjs 错误,需要在进入目的地之前完成 - 堆栈进一步展开到 VirtualScheduler 的 运行ning flush() 方法,which now dequeues the second scheduled debounced action 因为它是同步添加的计划工作数组,在此之前刷新完成。请记住,到目前为止,我们只调用了
scheduler.flush()
一次,这是我们此时返回的函数。 - 第二个预定的去抖动工作是 运行,但是
this.hasValue === false
因为第一个预定的去抖动工作设置了它,所以 debounceTime 运算符不会发出任何东西。 - 堆栈展开到我们的第一个
scheduler.flush()
- 我们
console.log('CHECK CORRECT STATE FOR PROGRESS 25')
- 所有其他
scheduler.flush()
调用什么都不做,因为没有任何安排。
这在技术上是一个错误,但是没有人 运行 进入它并不奇怪,因为 运行 没有任何延迟地同步去抖动违背了它的要点, 除了当你在测试时,当然 。 This ticket is basically the same thing and OJ says RxJS doesn't make reentrancy guarantees, but I that might be up for debate in this case. I've filed a PR with the fix to discuss
请记住,这个错误不会解决您关于排序的基本问题,但会阻止操作被吞没。
我不知道如果您想保持 100% 的同步行为 (VirtualScheduler),您会如何做您想做的事情。在去抖动之间,您需要某种方法来屈服于您的测试。对我来说,当我编写集成测试时,如果有的话,我很少模拟。例如让去抖实际上自然地去抖,或者通过模拟 setTimeout 来更快地推进它们,但 仍然保持它们异步 这将返回到你的测试允许你检查状态,但也让你的测试异步。
任何想要复制的人,here's the StackBlitz code I used
答案是 re-write 异步测试。另外 note-worthy 是我必须通过返回 Observable.fromPromise
而不是常规的 Observable.of
来模拟 ajax 请求,否则它们仍然会被反跳吞没。这些方面的东西(使用笑话):
describe('my integration test', () => {
const scheduler = new VirtualTimeScheduler();
beforeEach(() => {
// Fake ajax responses
const ajax = request => (
Observable.fromPromise(new Promise((resolve) => {
if (request.url === '/check_url_1') {
resolve({ response: { progress: 25, check_url: '/check_url_2' } });
} else if (request.url === '/check_url_2') {
resolve({ response: { progress: 50, check_url: '/check_url_3' } });
} else {
resolve({ response: { progress: 100 } });
}
}))
);
store = configureStore(defaultState, { ajax, timerInterval: 1 });
});
it('should update the store properly after each call', (done) => {
let i = 0;
store.subscribe(() => {
switch (i) {
case 0:
console.log('CHECK CORRECT STATE FOR PROGRESS 0');
break;
case 1:
console.log('CHECK CORRECT STATE FOR PROGRESS 25');
break;
case 2:
console.log('CHECK CORRECT STATE FOR PROGRESS 50');
break;
case 3:
console.log('CHECK CORRECT STATE FOR PROGRESS 100');
done();
break;
default:
}
i += 1;
});
store.dispatch({ checkUrl: '/check_url_1', progress: 0, type: 'PROCESSING' });
});
});
我还通过将计时器间隔作为依赖项传递来将其设置为 1。在我的史诗中我是这样设置的:.debounceTime(dependencies.timerInterval || 1000)