Mock = { calls = {}, return_values = {} } -- we store all calls and requested return values indexed -- by the mock object, we don't want to hold onto references -- when they should be garbage collected to make the tables -- be weak setmetatable(Mock.calls, { __mode="k" }) setmetatable(Mock.return_values, { __mode="k" }) function Mock.__call(mock, ...) Mock.calls[mock] = Mock.calls[mock] or {} local calls = Mock.calls[mock] calls[#calls+1] = {...} local return_values = Mock.return_values[mock] if return_values and return_values[#calls] then return unpack(return_values[#calls]) end end function Mock.__index(mock, key) local new_mock = Mock:new() rawset(mock, key, new_mock) return new_mock end function Mock:new() local mock = { returns = self.returns, then_returns = self.returns } setmetatable(mock, self) return mock end function Mock:returns(...) if getmetatable(self) ~= Mock then error("returns must be called with : operator", 2) end local return_values = Mock.return_values[self] or {} return_values[#return_values+1] = {...} Mock.return_values[self] = return_values return self end matchers = matchers or {} function matchers.was_called(target, value) if getmetatable(target) ~= Mock then return false, "target must be a Mock" end local calls = Mock.calls[target] or {} if #calls ~= value then return false, "expecting "..tostring(value).." calls, actually "..#calls end return true end function matchers.was_called_with(target, ...) if getmetatable(target) ~= Mock then return false, "target must be a Mock" end local calls = Mock.calls[target] or {} if #calls ~= 1 then return false, "expecting "..tostring(1).." call, actually "..#calls end local params = calls[1] or {} local args = {...} if #args ~= #params then return false, "expecting "..#args.." parameters, actually "..#params end for i=1,#args do if args[i] ~= params[i] then return false, "expecting parameter #"..tostring(i).." to be "..tostring(args[i]).." actually "..tostring(params[i]) end end return true end