【问题标题】:How do I test a Node.JS Script that is not a module and runs as soon as it's called?如何测试不是模块并在调用后立即运行的 Node.JS 脚本?
【发布时间】:2021-05-18 16:55:55
【问题描述】:

我喜欢用 Javascript 代替 bash 脚本。

假设一个名为 start.js 的人为脚本使用 node start.js 运行:

const shelljs = require("shelljs")

if (!shelljs.which("serve")) {
  shelljs.echo("'serve' is missing, please run 'npm ci'")
  process.exit(1)
}

shelljs.exec("serve -s build -l 3000")

我该如何测试:

  1. 如果 serve 不可用,则永远不会调用 serve -s build -l 3000 并且程序以代码 1 退出。
  2. 如果serve 可用,则调用serve -s build -l 3000

我不介意嘲笑“shelljs”、process.exit 或其他任何东西。

我的主要问题是弄清楚如何在测试套件中使用 requireimport 这样一个无功能和无模块的文件,并让它在每个单独的测试中运行一次,并在 没有的情况下运行模拟实际上把它变成了一个 CommonJS/ES6 模块。

【问题讨论】:

  • 这应该作为 e2e 测试进行测试,即在单独的进程中使用 exec 调用它。您可能很难在测试范围内处理process.exit。如果您需要模拟一些破坏性的东西,请使用node -r 导入执行此操作的文件(与 Jest 模拟无关)。

标签: javascript node.js ecmascript-6 scripting jestjs


【解决方案1】:

只需模拟 shelljs 模块,并监视 process.exit 函数。

describe("start.js", () => {
  let shelljs
  let exitSpy

  beforeEach(() => {
    jest.mock("shelljs", () => {
      return {
        exec: jest.fn(),
        which: jest.fn(),
        echo: jest.fn(),
      }
    })
    shelljs = require("shelljs")
    exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {})
  });

  afterEach(() => {
    jest.resetModules()
    jest.resetAllMocks()
  })

  it("should execute process.exit with code is 1 when 'serve' is not existed", () => {
    shelljs.which.mockReturnValue(false)

    require("./start")

    expect(shelljs.which).toHaveBeenCalledWith("serve");
    expect(shelljs.echo).toHaveBeenCalledWith("'serve' is missing, please run 'npm ci'")
    expect(exitSpy).toHaveBeenCalledWith(1)
    // expect(shelljs.exec).toHaveBeenCalled() // can not check like that, exitSpy will not "break" your code, it will be work well if you use if/else syntax
  });

  it("should execute serve when 'serve' is existed", () => {
    shelljs.which.mockReturnValue(true)

    require("./start")

    expect(shelljs.which).toHaveBeenCalledWith("serve");
    expect(shelljs.echo).not.toHaveBeenCalled()
    expect(exitSpy).not.toHaveBeenCalled()
    expect(shelljs.exec).toHaveBeenCalledWith("serve -s build -l 3000")
  });
})

在调用process.exit 时确保生产代码中断的另一种方法。模拟exit 函数抛出错误,然后期望shelljs.exec 不会被调用

describe("start.js", () => {
  let shelljs
  let exitSpy

  beforeEach(() => {
    jest.mock("shelljs", () => {
      return {
        exec: jest.fn(),
        which: jest.fn(),
        echo: jest.fn(),
      }
    })
    shelljs = require("shelljs")
    
    exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {
      throw new Error("Mock");
    })
  });

  afterEach(() => {
    jest.resetModules()
    jest.resetAllMocks()
  })

  it("should execute process.exit with code is 1 when 'serve' is not existed", () => {
    shelljs.which.mockReturnValue(false)

    expect.assertions(5)
    try {
      require("./start")
    } catch (error) {
      expect(error.message).toEqual("Mock")
    }

    expect(shelljs.which).toHaveBeenCalledWith("serve");
    expect(shelljs.echo).toHaveBeenCalledWith("'serve' is missing, please run 'npm ci'")
    expect(exitSpy).toHaveBeenCalledWith(1)
    expect(shelljs.exec).not.toHaveBeenCalled()
  });

  it("should execute serve when 'serve' is existed", () => {
    shelljs.which.mockReturnValue(true)

    require("./start")

    expect(shelljs.which).toHaveBeenCalledWith("serve");
    expect(shelljs.echo).not.toHaveBeenCalled()
    expect(exitSpy).not.toHaveBeenCalled()
    expect(shelljs.exec).toHaveBeenCalledWith("serve -s build -l 3000")
  });
})

【讨论】:

  • 这行得通!但这里的问题是,在实际代码中,process.exit(1) 在此时停止运行脚本。但是由于它在测试中被监视,它只会继续执行代码。有什么办法可以阻止吗?
  • 发现 another question 与您在此处所做的完全相同,即在间谍上抛出异常。我希望你只保留你的底层解决方案并使用expect().toThrow(),但这绝对是正确的答案并解决了我的问题,所以谢谢!
猜你喜欢
  • 1970-01-01
  • 2019-06-11
  • 1970-01-01
  • 1970-01-01
  • 2019-03-21
  • 1970-01-01
  • 2018-09-07
  • 1970-01-01
  • 2012-03-22
相关资源
最近更新 更多