Search code examples
unit-testingluafreeswitchlua-busted

Testing lua script with busted


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.


Solution

  • 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)