如何设置此 JS 代码以进行更好的测试?
How do I setup this JS code to do better testing?
大家好,我在使用 Jest 测试以下 JS 时遇到问题。它从 waitForWorker 开始。如果响应是 'working' 那么它会再次调用 waitForWorker() 。我尝试了 Jest 测试,但我不知道如何测试内部函数调用,我一直在研究并失败了。
const $ = require('jquery')
const axios = require('axios')
let workerComplete = () => {
window.location.reload()
}
async function checkWorkerStatus() {
const worker_id = $(".worker-waiter").data('worker-id')
const response = await axios.get(`/v1/workers/${worker_id}`)
return response.data
}
function waitForWorker() {
if (!$('.worker-waiter').length) {
return
}
checkWorkerStatus().then(data => {
// delay next action by 1 second e.g. calling api again
return new Promise(resolve => setTimeout(() => resolve(data), 1000));
}).then(worker_response => {
const working_statuses = ['queued', 'working']
if (worker_response && working_statuses.includes(worker_response.status)) {
waitForWorker()
} else {
workerComplete()
}
})
}
export {
waitForWorker,
checkWorkerStatus,
workerComplete
}
if (process.env.NODE_ENV !== 'test') $(waitForWorker)
我的一些测试如下,因为我无法与任何人核实。我不知道在测试中调用 await Worker.checkWorkerStatus() 两次是否是最好的方法,因为如果响应 data.status 是 'working'
,waitForWorker 应该再次调用它
import axios from 'axios'
import * as Worker from 'worker_waiter'
jest.mock('axios')
beforeAll(() => {
Object.defineProperty(window, 'location', {
value: { reload: jest.fn() }
})
});
beforeEach(() => jest.resetAllMocks() )
afterEach(() => {
jest.restoreAllMocks();
});
describe('worker is complete after 2 API calls a', () => {
const worker_id = Math.random().toString(36).slice(-5) // random string
beforeEach(() => {
axios.get
.mockResolvedValueOnce({ data: { status: 'working' } })
.mockResolvedValueOnce({ data: { status: 'complete' } })
jest.spyOn(Worker, 'waitForWorker')
jest.spyOn(Worker, 'checkWorkerStatus')
document.body.innerHTML = `<div class="worker-waiter" data-worker-id="${worker_id}"></div>`
})
it('polls the correct endpoint twice a', async() => {
const endpoint = `/v1/workers/${worker_id}`
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint]])
expect(data).toMatchObject({"status": "working"})
})
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint],[endpoint]])
expect(data).toMatchObject({"status": "complete"})
})
})
it('polls the correct endpoint twice b', async() => {
jest.mock('waitForWorker', () => {
expect(Worker.checkWorkerStatus).toBeCalled()
})
expect(Worker.waitForWorker).toHaveBeenCalledTimes(2)
await Worker.waitForWorker()
})
我认为您可以在这里做一些事情。
注入状态处理程序
您可以通过将 waitForWorker
依赖项和副作用注入到函数中来使它们更加明确,这使您可以完全黑盒测试系统并断言触发了正确的注入效果。这就是所谓的依赖注入。
function waitForWorker(onComplete, onBusy) {
// instead of calling waitForWorker call onBusy.
// instead of calling workerComplete call onComplete.
}
现在要进行测试,您真的只需要创建模拟函数即可。
const onComplete = jest.fn();
const onBusy = jest.fn();
并断言那些正在以您期望的方式被调用。这个函数也是异步的,所以你需要确保你的玩笑测试知道完成。我注意到您在测试中使用了 async
,但您当前的函数没有 return 未决承诺,因此测试将同步完成。
Return一个承诺
你可以 return 承诺并测试它的竞争。现在你的承诺不会暴露在 waitForWorker
.
之外
async function waitForWorker() {
let result = { status: 'empty' };
if (!$('.worker-waiter').length) {
return result;
}
try {
const working_statuses = ['queued', 'working'];
const data = await checkWorkerStatus();
if (data && working_statuses.includes(data.status)) {
await waitForWorker();
} else {
result = { status: 'complete' };
}
} catch (e) {
result = { status: 'error' };
}
return result;
}
以上示例将您的函数转换为 async
以提高可读性并消除了副作用。我 return 编辑了一个带有状态的异步结果,这很有用,因为 waitForWorker 可以完成许多分支。这将告诉您,根据您的 axios 设置,promise 最终将以某种状态完成。然后,您可以使用覆盖率报告来确保您关心的分支得到执行,而不必担心测试内部实现细节。
如果您确实想测试内部实现细节,您可能需要合并我上面提到的一些注入原则。
async function waitForWorker(request) {
// ...
try {
const working_statuses = ['queued', 'working'];
const data = await request();
} catch (e) {
// ...
}
// ...
}
然后您可以向其中注入任何函数,甚至是模拟函数,并确保它以您想要的方式调用,而无需模拟 axios。在您的应用程序中,您只需注入 checkWorkerStatus
.
const result = await waitForWorker(checkWorkerStatus);
if (result.status === 'complete') {
workerComplete();
}
大家好,我在使用 Jest 测试以下 JS 时遇到问题。它从 waitForWorker 开始。如果响应是 'working' 那么它会再次调用 waitForWorker() 。我尝试了 Jest 测试,但我不知道如何测试内部函数调用,我一直在研究并失败了。
const $ = require('jquery')
const axios = require('axios')
let workerComplete = () => {
window.location.reload()
}
async function checkWorkerStatus() {
const worker_id = $(".worker-waiter").data('worker-id')
const response = await axios.get(`/v1/workers/${worker_id}`)
return response.data
}
function waitForWorker() {
if (!$('.worker-waiter').length) {
return
}
checkWorkerStatus().then(data => {
// delay next action by 1 second e.g. calling api again
return new Promise(resolve => setTimeout(() => resolve(data), 1000));
}).then(worker_response => {
const working_statuses = ['queued', 'working']
if (worker_response && working_statuses.includes(worker_response.status)) {
waitForWorker()
} else {
workerComplete()
}
})
}
export {
waitForWorker,
checkWorkerStatus,
workerComplete
}
if (process.env.NODE_ENV !== 'test') $(waitForWorker)
我的一些测试如下,因为我无法与任何人核实。我不知道在测试中调用 await Worker.checkWorkerStatus() 两次是否是最好的方法,因为如果响应 data.status 是 'working'
,waitForWorker 应该再次调用它import axios from 'axios'
import * as Worker from 'worker_waiter'
jest.mock('axios')
beforeAll(() => {
Object.defineProperty(window, 'location', {
value: { reload: jest.fn() }
})
});
beforeEach(() => jest.resetAllMocks() )
afterEach(() => {
jest.restoreAllMocks();
});
describe('worker is complete after 2 API calls a', () => {
const worker_id = Math.random().toString(36).slice(-5) // random string
beforeEach(() => {
axios.get
.mockResolvedValueOnce({ data: { status: 'working' } })
.mockResolvedValueOnce({ data: { status: 'complete' } })
jest.spyOn(Worker, 'waitForWorker')
jest.spyOn(Worker, 'checkWorkerStatus')
document.body.innerHTML = `<div class="worker-waiter" data-worker-id="${worker_id}"></div>`
})
it('polls the correct endpoint twice a', async() => {
const endpoint = `/v1/workers/${worker_id}`
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint]])
expect(data).toMatchObject({"status": "working"})
})
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint],[endpoint]])
expect(data).toMatchObject({"status": "complete"})
})
})
it('polls the correct endpoint twice b', async() => {
jest.mock('waitForWorker', () => {
expect(Worker.checkWorkerStatus).toBeCalled()
})
expect(Worker.waitForWorker).toHaveBeenCalledTimes(2)
await Worker.waitForWorker()
})
我认为您可以在这里做一些事情。
注入状态处理程序
您可以通过将 waitForWorker
依赖项和副作用注入到函数中来使它们更加明确,这使您可以完全黑盒测试系统并断言触发了正确的注入效果。这就是所谓的依赖注入。
function waitForWorker(onComplete, onBusy) {
// instead of calling waitForWorker call onBusy.
// instead of calling workerComplete call onComplete.
}
现在要进行测试,您真的只需要创建模拟函数即可。
const onComplete = jest.fn();
const onBusy = jest.fn();
并断言那些正在以您期望的方式被调用。这个函数也是异步的,所以你需要确保你的玩笑测试知道完成。我注意到您在测试中使用了 async
,但您当前的函数没有 return 未决承诺,因此测试将同步完成。
Return一个承诺
你可以 return 承诺并测试它的竞争。现在你的承诺不会暴露在 waitForWorker
.
async function waitForWorker() {
let result = { status: 'empty' };
if (!$('.worker-waiter').length) {
return result;
}
try {
const working_statuses = ['queued', 'working'];
const data = await checkWorkerStatus();
if (data && working_statuses.includes(data.status)) {
await waitForWorker();
} else {
result = { status: 'complete' };
}
} catch (e) {
result = { status: 'error' };
}
return result;
}
以上示例将您的函数转换为 async
以提高可读性并消除了副作用。我 return 编辑了一个带有状态的异步结果,这很有用,因为 waitForWorker 可以完成许多分支。这将告诉您,根据您的 axios 设置,promise 最终将以某种状态完成。然后,您可以使用覆盖率报告来确保您关心的分支得到执行,而不必担心测试内部实现细节。
如果您确实想测试内部实现细节,您可能需要合并我上面提到的一些注入原则。
async function waitForWorker(request) {
// ...
try {
const working_statuses = ['queued', 'working'];
const data = await request();
} catch (e) {
// ...
}
// ...
}
然后您可以向其中注入任何函数,甚至是模拟函数,并确保它以您想要的方式调用,而无需模拟 axios。在您的应用程序中,您只需注入 checkWorkerStatus
.
const result = await waitForWorker(checkWorkerStatus);
if (result.status === 'complete') {
workerComplete();
}