如何测试不是模块并在调用后立即运行的 Node.JS 脚本?

问题描述

我喜欢用 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 模块。

解决方法

只需模拟 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",}
    })
    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")
  });
})