带有 setTimeout 的递归函数使开玩笑测试超过最大调用堆栈大小



我有一个反应代码,需要检查服务,直到它回答正常。 在浏览器中,没有问题。 当我编写测试时,我需要模拟服务和 setTimeout,以免破坏它。 现在,我面临着一个问题:最大调用堆栈大小。

简体版:

class Retriable extends Component {
doRefreshStatus(){
get('url', response => {
if(response !== 'OK') {
createRefresh()
} 
})
}
createRefresh() {
setTimeout(this.doRefreshStatus, 5000);
}
}
Test:
mock get -> will return immediately the response
mock setTimeout -> will return immediately
test-> 
mock get -> return 'PROCESSING'; // will keep retrying
assert...
mock get -> return 'OK' //will stop the retries

真实代码:

import React, { Component } from 'react';
import { doGet } from '../../src/commons/connection';
class Retriable extends Component {
constructor(props) {
super(props);
this.state = {
status: null
};
this.createRefresh = this.createRefresh.bind(this);
this.doRefreshStatus = this.doRefreshStatus.bind(this);
}
componentWillMount() {
this.doRefreshStatus();
}
doRefreshStatus() {
doGet('blablabla', status => {
if (status !== 'OK') {
this.createRefresh();
}
this.setStatus({status});
});
}
createRefresh() {
setTimeout(() => {
this.doRefreshStatus();
}, 5000);
}
render() {
return (<span id='current-status'>{this.state.status}</span>);
}
}
export default Retriable;

测试:


jest.mock('../../src/commons/connection');
import { mountEvaSceneWithOnlyProps } from '../test-utils';
import { doGet } from '../../src/commons/connection';
import Retriable from './Retriable';
// eslint-disable-next-line no-undef
global.setTimeout = callback => {
callback();
};
const mockScenario = status => {
doGet.mockClear();
doGet.mockImplementation((service, callback) => {
if (service === 'blablabla') {
callback({ status });
} else {
throw new Error('Unknown service: ', service);
}
});
};
describe('Retriable concept test', () => {
mockScenario('PROCESSING');
//basicly mount
let wrapper = mountEvaSceneWithOnlyProps(Retriable, {});
it('Will not break due to retry indefinitely', () => {
expect(wrapper.find('#current-status').text()).toBe('PROCESSING');
mockScenario('OK');
expect(wrapper.find('#current-status').text()).toBe('OK');
});
});

范围错误:超出最大调用堆栈大小

> 1 | import React, { Component } from 'react';
| ^
2 | import { doGet } from '../../src/commons/connection';
3 | 
4 | class Retriable extends Component {
at Retriable.createRefresh (test/commons/Retriable.js:1:1)
at createRefresh (test/commons/Retriable.js:22:14)
at callback (test/commons/Retriable.test.js:17:7)
at Retriable.doRefreshStatus (test/commons/Retriable.js:20:5)
at doRefreshStatus (test/commons/Retriable.js:30:12)
at callback (test/commons/Retriable.test.js:9:3)
at Retriable.setTimeout (test/commons/Retriable.js:29:5)

我发现有一个计时器选项:


jest.mock('../../src/commons/connection');
import { mountEvaSceneWithOnlyProps } from '../test-utils';
import { doGet } from '../../src/commons/connection';
import Retriable from './Retriable';
// eslint-disable-next-line no-undef
jest.useFakeTimers(); //-------------------------- IMPORTANT
const mockScenario = status => {
doGet.mockClear();
doGet.mockImplementation((service, callback) => {
if (service === 'blablabla') {
callback({ status });
} else {
throw new Error('Unknown service: ', service);
}
});
};
describe('Retriable concept test', () => {
it('Will not break due to retry indefinitely - multiple results', () => {
mockScenario('PROCESSING');
let wrapper = mountEvaSceneWithOnlyProps(Retriable, {});
expect(wrapper.find('#current-status').text()).toBe('PROCESSING');
mockScenario('OTHER');
jest.runOnlyPendingTimers(); // RUN ALL TIMERS UNTIL THIS POINT
expect(wrapper.find('#current-status').text()).toBe('OTHER');
mockScenario('OK');
jest.runOnlyPendingTimers(); // AGAIN
expect(wrapper.find('#current-status').text()).toBe('OK');
});
});