【问题标题】:How do I setup this JS code to do better testing?如何设置此 JS 代码以进行更好的测试?
【发布时间】:2020-10-08 20:09:39
【问题描述】:

大家好,我在使用 Jest 测试以下 JS 时遇到问题。它从 waitForWorker 开始。如果响应是“工作”,那么它会再次调用 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 为“工作”,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 被调用两次?
  • @loremdipso wait for worker 应该被调用两次,因为 axios 首先返回工作状态(代码运行,例如等待工作者),然后第二次再次调用状态完成。

标签: javascript jestjs jest-fetch-mock


【解决方案1】:

我认为您可以在这里做几件事。

注入状态处理程序

您可以通过将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();

并断言它们是以您期望的方式被调用的。这个函数也是异步的,所以你需要确保你的 jest 测试知道完成。我注意到您在测试中使用了async,但您当前的函数没有返回待处理的承诺,因此测试将同步完成。

返回一个承诺

你可以只返回一个承诺并测试它的竞争。现在你的承诺没有暴露在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 以提高可读性并消除副作用。我返回了一个带有状态的异步结果,这很有用,因为 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();
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-21
    • 2016-07-04
    • 2014-09-30
    • 2017-01-30
    • 1970-01-01
    • 2011-03-11
    相关资源
    最近更新 更多