如何在 JEST 中测试并行模拟数据请求,同时模拟具有 500 毫秒阈值的缓存响应
How to test parallel, mocked data requests in JEST whilst simulating cached responses with a 500ms threshold
测试的目的是模拟获取不同数据源的并行请求。我为每个请求引入了人工延迟,并在一段时间后 return 一个带有识别数字的简单字符串,以查看数据是否已从缓存中加载(500 毫秒内的请求)。因此,对于在 500 毫秒内加载的数据,输出应为“A1B1”,否则,在 500 毫秒后,应为“A2B2”,依此类推。
// index.test.js
const { wait } = require('./util/wait.js');
const { requestAandB, requestBandC } = require('./index.js');
test('Test cache timings ', () => Promise.all([
// send two requests in parallel after 0 ms (immediately)
wait(0).then(() => Promise.all([
expect(requestAandB()).resolves.toEqual('A1B1'),
expect(requestBandC()).resolves.toEqual('B1C1'),
])),
// send two requests in parallel after 480 ms
wait(480).then(() => Promise.all([
expect(requestAandB()).resolves.toEqual('A1B1'),
expect(requestBandC()).resolves.toEqual('B1C1'),
])),
// send two requests in parallel after 520 ms
wait(520).then(() => Promise.all([
expect(requestAandB()).resolves.toEqual('A2B2'),
expect(requestBandC()).resolves.toEqual('B2C2'),
])),
]));
这就是我模拟数据加载的方式
// get-data.js
async function mockLoading(str) {
// introduce some latency
const waitDuration = Math.round(Math.random() * (WAIT_MAX - WAIT_MIN)) + WAIT_MIN;
await wait(waitDuration);
// create or increase counter every time the function is being called
counters[str] = counters[str] === undefined ? 1 : counters[str] + 1;
return str + counters[str];
}
module.exports = {
loadDataA: async () => mockLoading('A'),
loadDataB: async () => mockLoading('B'),
loadDataC: async () => mockLoading('C'),
}
最后,在测试文件中导入方法requestAandB
和requestBandC
的实现:
const { loadDataA, loadDataB, loadDataC } = require('./model/get-data.js');
const all = Promise.all([loadDataA(), loadDataB(), loadDataC()])
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async function requestAandB() {
const temp = await all
await delay(Math.random() * 510)
return temp.filter((_, i) => i < 2).join("")
}
async function requestBandC() {
const temp = await all
await delay(Math.random() * 510)
return temp.filter((_, i) => i > 0).join("")
}
module.exports = { requestAandB, requestBandC }
数据“A1B1”、“B1C1”的测试很好,但由于延迟(在 mockLoading
函数中)始终高于 500 毫秒阈值,我无法获得正确的结果数据 return 之后编辑。实际上,“A2B2”和“B2C2”总是失败。
有人知道我在这里错过了什么吗?
看来您的第一个测试也不能正常工作。
So for data loaded within 500ms the output should be "A1B1", else, after 500ms, it should be "A2B2" and so on.
但后来你说
Tests for the data "A1B1", "B1C1" are fine, but because the latency (in the mockLoading function) is always above the 500ms threshold, I am not able to get the right results for data returned after that. In effect, "A2B2" and "B2C2" always fail.
最简单的方法是将时间传递给 requestAandB
和 requestBandC
,因为使用 Math.random()
可以给你随机数并且可能会随机失败测试。所以试试这个方法:
test('Test cache timings ', () =>
Promise.all([
// send two requests in parallel after 0 ms (immediately)
expect(requestAandB(0)).resolves.toEqual('A1B1'),
expect(requestBandC(0)).resolves.toEqual('B1C1'),
// send two requests in parallel after 480 ms
expect(requestAandB(480)).resolves.toEqual('A1B1'),
expect(requestBandC(480)).resolves.toEqual('B1C1'),
// send two requests in parallel after 520 ms
expect(requestAandB(520)).resolves.toEqual('A2B2'),
expect(requestBandC(520)).resolves.toEqual('B2C2'),
// send two requests in parallel after 360 ms
expect(requestAandB(360)).resolves.toEqual('A1B1'),
expect(requestBandC(360)).resolves.toEqual('B1C1'),
// send two requests in parallel after 750 ms
expect(requestAandB(750)).resolves.toEqual('A2B2'),
expect(requestBandC(750)).resolves.toEqual('B2C2'),
]));
index.js
const { loadDataA, loadDataB, loadDataC } = require('./get-data.js');
async function requestAandB(time) {
const temp = await Promise.all([
loadDataA(time),
loadDataB(time),
loadDataC(time),
]); // don't use global Promise outside of this function
return temp.filter((_, i) => i < 2).join('');
}
async function requestBandC(time) {
const temp = await Promise.all([
loadDataA(time),
loadDataB(time),
loadDataC(time),
]); // don't use global Promise outside of this function
return temp.filter((_, i) => i > 0).join('');
}
module.exports = { requestAandB, requestBandC };
get-data.js
// get-data.js
const { wait } = require('./wait.js');
async function mockLoading(time, str) {
await wait(time);
// here is the logic if time is less then 500ms then append 1 else append 2
let label = str;
if (Math.ceil(time / 500) <= 1) {
label += '1';
} else {
label += '2';
}
return label;
}
module.exports = {
loadDataA: async time => mockLoading(time, 'A'),
loadDataB: async time => mockLoading(time, 'B'),
loadDataC: async time => mockLoading(time, 'C'),
};
这确实是一个棘手的问题,但我通过执行以下操作设法破解了它:
把Promise.all
移到各自的函数里面;这是因为 loadData 函数的承诺只在模块加载时调用一次(感谢@Teneff 和@Observer 的提示)
缓存函数的实现来缓存预期的承诺(不是预期的结果...),一旦满足某些条件就返回 loadData(缓存)遇见了
我们根本不需要更改 mockLoading
函数。 OP 需要执行客户端代码,然后调用外部 API 来模拟跨 n 个异步请求的高差异。换句话说,我们必须消除(或最小化)并行异步请求的数据冲突风险,以及如何在 Jest 中测试一些模拟案例。
一些代码让整个过程更清晰:
const { loadDataA, loadDataB, loadDataC } = require('./model/get-data.js');
// we tackle this problem using memoization
// when `useCache` is called, we cache the loadData function name
// we then assign 1. a timestamp property to it and 2. a pointer to the promise itself
const CACHE_DURATION = 500;
const cache = {};
function useCache(fn) {
const cached = cache[fn.name];
const now = Date.now();
if (!cached || cached.ts < now - CACHE_DURATION) {
cache[fn.name] = { ts: now, promise: fn() }
}
return cache[fn.name].promise;
}
async function requestAandB() {
const [a, b] = await Promise.all([ loadDataA, loadDataB ].map(useCache));
return a + b;
}
async function requestBandC() {
const [b, c] = await Promise.all([ loadDataB, loadDataC ].map(useCache));
return b + c;
}
module.exports = { requestAandB, requestBandC }
谢谢
测试的目的是模拟获取不同数据源的并行请求。我为每个请求引入了人工延迟,并在一段时间后 return 一个带有识别数字的简单字符串,以查看数据是否已从缓存中加载(500 毫秒内的请求)。因此,对于在 500 毫秒内加载的数据,输出应为“A1B1”,否则,在 500 毫秒后,应为“A2B2”,依此类推。
// index.test.js
const { wait } = require('./util/wait.js');
const { requestAandB, requestBandC } = require('./index.js');
test('Test cache timings ', () => Promise.all([
// send two requests in parallel after 0 ms (immediately)
wait(0).then(() => Promise.all([
expect(requestAandB()).resolves.toEqual('A1B1'),
expect(requestBandC()).resolves.toEqual('B1C1'),
])),
// send two requests in parallel after 480 ms
wait(480).then(() => Promise.all([
expect(requestAandB()).resolves.toEqual('A1B1'),
expect(requestBandC()).resolves.toEqual('B1C1'),
])),
// send two requests in parallel after 520 ms
wait(520).then(() => Promise.all([
expect(requestAandB()).resolves.toEqual('A2B2'),
expect(requestBandC()).resolves.toEqual('B2C2'),
])),
]));
这就是我模拟数据加载的方式
// get-data.js
async function mockLoading(str) {
// introduce some latency
const waitDuration = Math.round(Math.random() * (WAIT_MAX - WAIT_MIN)) + WAIT_MIN;
await wait(waitDuration);
// create or increase counter every time the function is being called
counters[str] = counters[str] === undefined ? 1 : counters[str] + 1;
return str + counters[str];
}
module.exports = {
loadDataA: async () => mockLoading('A'),
loadDataB: async () => mockLoading('B'),
loadDataC: async () => mockLoading('C'),
}
最后,在测试文件中导入方法requestAandB
和requestBandC
的实现:
const { loadDataA, loadDataB, loadDataC } = require('./model/get-data.js');
const all = Promise.all([loadDataA(), loadDataB(), loadDataC()])
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async function requestAandB() {
const temp = await all
await delay(Math.random() * 510)
return temp.filter((_, i) => i < 2).join("")
}
async function requestBandC() {
const temp = await all
await delay(Math.random() * 510)
return temp.filter((_, i) => i > 0).join("")
}
module.exports = { requestAandB, requestBandC }
数据“A1B1”、“B1C1”的测试很好,但由于延迟(在 mockLoading
函数中)始终高于 500 毫秒阈值,我无法获得正确的结果数据 return 之后编辑。实际上,“A2B2”和“B2C2”总是失败。
有人知道我在这里错过了什么吗?
看来您的第一个测试也不能正常工作。
So for data loaded within 500ms the output should be "A1B1", else, after 500ms, it should be "A2B2" and so on.
但后来你说
Tests for the data "A1B1", "B1C1" are fine, but because the latency (in the mockLoading function) is always above the 500ms threshold, I am not able to get the right results for data returned after that. In effect, "A2B2" and "B2C2" always fail.
最简单的方法是将时间传递给 requestAandB
和 requestBandC
,因为使用 Math.random()
可以给你随机数并且可能会随机失败测试。所以试试这个方法:
test('Test cache timings ', () =>
Promise.all([
// send two requests in parallel after 0 ms (immediately)
expect(requestAandB(0)).resolves.toEqual('A1B1'),
expect(requestBandC(0)).resolves.toEqual('B1C1'),
// send two requests in parallel after 480 ms
expect(requestAandB(480)).resolves.toEqual('A1B1'),
expect(requestBandC(480)).resolves.toEqual('B1C1'),
// send two requests in parallel after 520 ms
expect(requestAandB(520)).resolves.toEqual('A2B2'),
expect(requestBandC(520)).resolves.toEqual('B2C2'),
// send two requests in parallel after 360 ms
expect(requestAandB(360)).resolves.toEqual('A1B1'),
expect(requestBandC(360)).resolves.toEqual('B1C1'),
// send two requests in parallel after 750 ms
expect(requestAandB(750)).resolves.toEqual('A2B2'),
expect(requestBandC(750)).resolves.toEqual('B2C2'),
]));
index.js
const { loadDataA, loadDataB, loadDataC } = require('./get-data.js');
async function requestAandB(time) {
const temp = await Promise.all([
loadDataA(time),
loadDataB(time),
loadDataC(time),
]); // don't use global Promise outside of this function
return temp.filter((_, i) => i < 2).join('');
}
async function requestBandC(time) {
const temp = await Promise.all([
loadDataA(time),
loadDataB(time),
loadDataC(time),
]); // don't use global Promise outside of this function
return temp.filter((_, i) => i > 0).join('');
}
module.exports = { requestAandB, requestBandC };
get-data.js
// get-data.js
const { wait } = require('./wait.js');
async function mockLoading(time, str) {
await wait(time);
// here is the logic if time is less then 500ms then append 1 else append 2
let label = str;
if (Math.ceil(time / 500) <= 1) {
label += '1';
} else {
label += '2';
}
return label;
}
module.exports = {
loadDataA: async time => mockLoading(time, 'A'),
loadDataB: async time => mockLoading(time, 'B'),
loadDataC: async time => mockLoading(time, 'C'),
};
这确实是一个棘手的问题,但我通过执行以下操作设法破解了它:
把
Promise.all
移到各自的函数里面;这是因为 loadData 函数的承诺只在模块加载时调用一次(感谢@Teneff 和@Observer 的提示)缓存函数的实现来缓存预期的承诺(不是预期的结果...),一旦满足某些条件就返回 loadData(缓存)遇见了
我们根本不需要更改 mockLoading
函数。 OP 需要执行客户端代码,然后调用外部 API 来模拟跨 n 个异步请求的高差异。换句话说,我们必须消除(或最小化)并行异步请求的数据冲突风险,以及如何在 Jest 中测试一些模拟案例。
一些代码让整个过程更清晰:
const { loadDataA, loadDataB, loadDataC } = require('./model/get-data.js');
// we tackle this problem using memoization
// when `useCache` is called, we cache the loadData function name
// we then assign 1. a timestamp property to it and 2. a pointer to the promise itself
const CACHE_DURATION = 500;
const cache = {};
function useCache(fn) {
const cached = cache[fn.name];
const now = Date.now();
if (!cached || cached.ts < now - CACHE_DURATION) {
cache[fn.name] = { ts: now, promise: fn() }
}
return cache[fn.name].promise;
}
async function requestAandB() {
const [a, b] = await Promise.all([ loadDataA, loadDataB ].map(useCache));
return a + b;
}
async function requestBandC() {
const [b, c] = await Promise.all([ loadDataB, loadDataC ].map(useCache));
return b + c;
}
module.exports = { requestAandB, requestBandC }
谢谢