I'm attempting to test our Freeswitch lua scripts with busted and am running into a snag. The gist of it is that I need to be able to spy on code like the following
local req_host = session:getVariable('sip_req_host')
session:setVariable('curl_timeout', 0)
But I can't seem to figure out how to build the object that I should set _G.session to. The best / only good example of how to use busted I can find is at https://github.com/chris-allnutt/unit-tested-corona/blob/master/mocks/button.lua but it appears to use the same simple syntax for building a mock object that the busted docs does.
local button = {
x = 0,
y = 0,
addEventListener = function() end
}
I can see how this would work for simple functions that don't need to return anything but I need to be able to get and set variables in the session object using the getVariable and setVariable functions. My simple mock object is as follows:
Session = {}
Session.__index = Session
function Session.create(params)
local session = {}
setmetatable(session, Session)
session.params = params
return session
end
function Session:getVariable(key)
return self.params[key]
end
function Session:setVariable(key, val)
self.params[key] = val
end
function Session:execute(cmd, code)
end
and the test is as follows
require "busted"
require("test_utils")
describe("Test voip lua script", function()
it('Test webrtc bad domain', function()
domain = 'rtc.baddomain.com';
session_params = {['sip_req_host'] = domain,
['sip_req_user'] = 'TEST-WebRTC-Client',
["sip_from_user"] = 'testwebrtc_p_12345',
['sip_call_id'] = 'test@call_id',
['sip_authorized'] = 'false'}
exec_str = 'sofia_contact TEST-WebRTC-Client@'..domain;
api_params = {[exec_str] = 'error/user_not_registered'}
_G.session = mock(Session.create(session_params), 'execute')
_G.api = API.create(api_params)
_G.freeswitch = Freeswitch.create()
dofile("tested_script.lua")
assert.spy(_G.session.execute).called_with("respond", "407")
end)
end)
I end up with the following exception. /usr/local/share/lua/5.2/luassert/spy.lua:78: attempt to index a function value
This exception is being thrown by luassert, a dependency of the busted library, in the following if statement
77:local function called_with(state, arguments)
78: if rawget(state, "payload") and rawget(state, "payload").called_with then
79: return state.payload:called_with(arguments)
80: else
81: error("'called_with' must be chained after 'spy(aspy)'")
82: end
83:end
I'm quite new to lua so it seems likely that I'm just missing some obvious part of the language but any help or pointers would be greatly appreciated.
So the answer that I found after another day of debugging was that yes, you do need to use a table as the object that you call mock on. However, because lua is a very forgiving language when it comes to building objects with callable parameters this still ends up working. I've built a wrapper around the object for reasons unrelated to this question but you can see what I ultimately made work below.
function SessionConstructor.create(params)
local session_constructor = {}
setmetatable(session_constructor, SessionConstructor)
session_constructor.session = {}
session_constructor.session.params = params
session_constructor.session.getVariable = function(self,key)
return self.params[key]
end
session_constructor.session.setVariable = function(self, key, val)
self.params[key] = val
end
session_constructor.session.execute = function(self, cmd, code)
end
return session_constructor
end
function SessionConstructor:construct()
return self.session
end
One important caveat, because of how you have to pass self into functions that will be called with lua's ":" syntax the method of spying on what functions was called with does change as seen in test file below.
require "busted"
require "test_utils"
describe("Test voip lua script", function()
it('Test webrtc bad domain', function()
domain = 'rtc.baddomain.com';
session_params = {['sip_req_host'] = domain,
['sip_req_user'] = 'TEST-WebRTC-Client',
["sip_from_user"] = 'testwebrtc_p_12345',
['sip_call_id'] = 'test@call_id',
['sip_authorized'] = 'false'}
local sess_con = SessionConstructor.create(session_params)
exec_str = 'sofia_contact TEST-WebRTC-Client@'..domain;
local api_con = APIConstructor.create()
api_con:expect_exec(exec_str, 'error/user_not_registered')
_G.session = mock(sess_con:construct())
_G.api = mock(api_con:construct())
_G.freeswitch = create_freeswitch()
dofile("tested_script.lua")
assert.spy(session.execute).was.called_with(session, "respond", "407")
assert.spy(session.execute).was_not.called_with("respond", "407") --This is unfortunate
end)
end)