5

I have a Lua function that returns false followed by error message and want to test its behavior using the busted testing framework. Right now I am doing it a bit like this:

function safe_divide(a, b)
    if b > 0 then -- buggy! should be b ~= 0 
        return a / b
    else
        return false, "division by zero"
    end
end

describe("safe_divide", function()
    it("can divide by positive numbers", function()
        local ok, err = safe_divide(10.0, 5.0)
        assert.truthy(ok)
        assert.are.same(2.0, ok)
    end)

    it("errors when dividing by zero", function()
        local ok, err = safe_divide(10.0, 0.0)
        assert.not_truthy(ok)
        assert.are.same("division by zero", err)
    end)

    it("can divide by negative numbers", function()
        local ok, err = safe_divide(-10.0, -5.0)
        assert.truthy(ok)
        assert.are.same(2.0, ok)
    end)
end)

There are two things that I don't like about my current approach:

  • Each test is 3 lines instead of a single clean line
  • When the 3rd test fails, busted just says that false is not a truthy value as expected, and never mentions the "divide by zero" error message.

Is there a way I can improve my test file to avoid these problems?

I think what I want to do is kind of similar to the has_error assertion in busted but that seems to be only for functions that actually raise exceptions, not for functions that return false followed by error message.

hugomg
  • 68,213
  • 24
  • 160
  • 246
  • [I'm in a hurry, can post an answer later.] Use [`assert`](https://www.lua.org/manual/5.3/manual.html#pdf-assert)? It turns "silent errors" into "loud errors". (And the common pattern for failure is `return nil, errmsg`, but `return false, errmsg` works just as well.) – nobody Sep 21 '17 at 19:42
  • That didn't work for me because `assert` adds line-number information to the error message. Busted then says that `"divide_spec.lua:17: division by zero"` doesn't match the expected `"division by zero"` – hugomg Sep 21 '17 at 19:56
  • You could write your own `assert` variant, such as:`local function myassert (ok, err) if ok then return ok else error (err) end end`. – Alban Linard Sep 22 '17 at 04:53
  • @AlbanLinard That is no different from just using assert directly. the `error` function also adds line number information. – hugomg Sep 22 '17 at 04:56

1 Answers1

3

Busted makes use of luassert, that can be extended with your own assertions.

For instance, the following code uses a user-defined assertion answers that takes as first parameter a table of the expected result, and the effective function results as remaining parameters.

local assert = require "luassert"

local function safe_divide(a, b)
    if b > 0 then -- buggy! should be b ~= 0
        return a / b
    else
        return false, "division by zero"
    end
end

local function answers(state, arguments)
  local expected = arguments[1]
  assert(type(expected) == "table")
  for i = 2, #arguments do
      if arguments[i] ~= expected[i-1] then
          state.failure_message = "unexpected result " .. tostring (i-1) .. ": " .. tostring (arguments [i])
          return false
      end
  end
  return true
end
assert:register("assertion", "answers", answers)

describe("safe_divide", function()
    it("can divide by positive numbers", function()
        assert.answers({ 2.0 }, safe_divide(10.0, 5.0))
    end)

    it("errors when dividing by zero", function()
        assert.answers({ false, "division by zero" }, safe_divide(10.0, 0.0))
    end)

    it("can divide by negative numbers", function()
        assert.answers({ 2.0 }, safe_divide(-10.0, -5.0))
    end)
end)

This code lacks correct formatting for the assertion message. You can look at the luaassert documentation, or at the predefined assertions. It contains a say module for translations of messages.

Alban Linard
  • 1,037
  • 9
  • 19
  • Little note. This code does not work if need check `nil` and error message. Also it possible register custom messages in say module. – moteus Sep 22 '17 at 07:29