使用 Busted 单元测试 Lua 代码时模拟本地导入

我很新手使用 Lua,我正在尝试测试我正在运行的一个带有 Nginx 服务器的脚本。我被推荐使用 Busted,但我似乎无法弄清楚如何模拟一些本地导入。

Lua 代码导入以下内容:

local http = require "resty.http"

在测试 _spec 文件中,我这样开始:

package.path = "files/?.lua;spec/?.lua;" .. package.path

_G.http = require('resty.fake_http')
local app = require('app')

我在 spec/resty/http 中创建了一个 fake_http.lua 文件。

但是当我运行一个虚拟测试时,我会收到以下错误消息:

suite spec/app_spec.lua
files/app.lua:3: module 'resty.http' not found:No LuaRocks module found for resty.http

这里我做错了什么呢?

点赞
用户1598293
用户1598293

有几个小问题阻止你的代码工作:

首先,你不能通过设置一个同名的全局变量来覆盖http局部变量。局部变量将始终遮盖全局变量。

其次,require仍然被调用,如果你在测试代码中删除了local,它仍然会覆盖全局http变量中保存的内容。

你需要的是一种方法让require在被调用时加载你的resty.fake_http模块。我可以想到三种方法:

1. 预加载模块

require函数使用两个表package.loadedpackage.preload来控制模块的加载时间和方式(详细信息在此)。 当require被调用时,它首先检查package.loaded[module]是否设置,并且如果设置了,返回该值。

这是模拟模块的第一个机会:

package.loaded["resty.http"] = require "resty.fake_http"
local app = require('app')

或者,如果package.loaded中没有条目,则会检查package.preload[module]是否有可以加载模块的函数:

package.preload["resty.http"] = function ()
  return require("resty.fake_http")
end
local app = require('app')

2. 更改package.path并覆盖模块

通过添加spec目录到路径中,您已经在做这件事。您所需要做的只是将假模块命名为原始模块的名称,它将被自动加载。 例如:test_spec:

package.path = "files/?.lua;spec/?.lua;" .. package.path
local app = require('app')

在测试的代码中,它将自动选择spec/resty/http.lua

这两种解决方案之间的区别在于,第二种方案只有在测试的代码实际上需要它时,才会**仅导入resty.fake_http**,而第一种方案则总是需要它。

3. Monkeypatch require

这是三种解决方案中最丑陋的一个,但它也可以完美运行。 require只是另一个全局变量,因此你也可以覆盖它:

local _original_require = require
function require(modname, ...)
  if modname == "resty.http" then
    -- implement the exception here
    return _original_require("resty.fake_http")
  end
  -- otherewise act as normal
  return _original_require(modname, ...)
end
local app = require('app')

第二种方法是最简单实用的,但第一种方法更加清晰和多功能。如果你可以花费30分钟阅读链接文档并了解require函数的工作原理,那么它可以帮助你解决未来更复杂的问题。

2019-05-02 10:16:05