Luassert

repository·master·Indexed 42 days ago

https://github.com/lunarmodules/luassert

An extension for Lua's built-in assertions providing a chainable API for testing. Features include spies, matchers, snapshots, custom formatting, and the ability to register custom assertions and matchers.

Tokens
1.9K
Snippets
6
Records
6
Agent score
4%

What's inside Luassert

  1. Use Snapshots to revert test changes

    master

    Snapshots allow you to capture the state of spies, stubs, parameters, and formatters, and then revert to that state. This is useful in after_each blocks to ensure test isolation.

    1. Create a snapshot with assert:snapshot().
    2. Call snapshot:revert() to undo changes made to spies, stubs, parameters, or formatters.
    describe("Showing use of snapshots", function()
      local snapshot
    
      before_each(function()
        snapshot = assert:snapshot()
      end)
    
      after_each(function()
        snapshot:revert()
      end)
    
      it("does some test", function()
        -- spies or stubs registered here, parameters changed, or formatters added
        -- will be undone in the after_each() handler.
      end)
    end)
  2. Use Luassert for basic assertions

    master

    Luassert extends Lua's built-in assertions with a chainable API. You can use assert.True, assert.is.True, assert.are.equal, and more. You can also negate assertions using not or is_not / is_not_true style syntax.

    Note on Lua Keywords: Because keywords like true, false, nil, function, and not are reserved in Lua, you cannot use them in dot-chaining (e.g., assert.is.true will fail). Instead, use underscores (is_true) or capitalization (is.True) to avoid compilation errors.

    assert = require("luassert")
    
    assert.True(true)
    assert.is.True(true)
    assert.is_true(true)
    assert.is_not.True(false)
    assert.is.Not.True(false)
    assert.is_not_true(false)
    assert.are.equal(1, 1)
    assert.has.errors(function() error("this should fail") end)
    assert.error.matches(function() error("foo bar") end, "^foo", nil, false)
  3. Configure table depth display

    master

    You can control how deeply nested tables are displayed in assertion failure messages by setting the TableFormatLevel parameter.

    • Set TableFormatLevel to a non-negative integer to limit display depth.
    • Set TableFormatLevel to -1 to display all levels.
    -- Limit table display to 2 levels
    assert:set_parameter("TableFormatLevel", 2)
    assert.are.same(testtable, {})
    
    -- Display all levels
    assert:set_parameter("TableFormatLevel", -1)
  4. Use Matchers for spy argument validation

    master

    Matchers allow flexible argument matching for called_with and returned_with assertions on spies/stubs. You can use the luassert.match module to create matchers like match._ (anything), match.is_string(), or match.has_match(pattern).

    Composite Matchers:

    • match.all_of(m1, m2, ...): Matches if all matchers pass.
    • match.any_of(m1, m2, ...): Matches if at least one matcher passes.
    • match.none_of(m1, m2, ...): Matches if no matchers pass.

    Reference Matching: If a spy is for a method that mutates an object, use match.is_ref(obj) to verify the object itself was passed.

    local assert = require 'luassert'
    local match = require 'luassert.match'
    local spy = require 'luassert.spy'
    
    local s = spy.new(function() end)
    s('foo')
    assert.spy(s).was.called_with(match._) -- arg1 is anything
    assert.spy(s).was.called_with(match.is_string()) -- arg1 is a string
    assert.spy(s).was.called_with(match.has_match('.oo')) -- arg1 contains pattern ".oo"
    
    -- Custom Matcher Example
    local function is_even(state, arguments)
      return function(value)
        return (value % 2) == 0
      end
    end
    assert:register("matcher", "even", is_even)
    
    -- Using custom matcher
    s(7)
    assert.spy(s).was.called_with(match.is_not_even())
  5. Extend Luassert with custom assertions

    master

    You can register your own assertions using assert:register(type, name, function, positive_msg, negative_msg).

    • type: Either "assertion" or "matcher".
    • name: The name used in the chain.
    • function: The implementation function.
    • positive_msg / negative_msg: (Optional) I18n keys for failure messages.

    When creating an assertion, the function receives (state, arguments). When creating a matcher, the function returns a function that takes a value and returns a boolean.

    local assert = require("luassert")
    local say    = require("say")
    
    local function has_property(state, arguments)
      local property = arguments[1]
      local table = arguments[2]
      for key, value in pairs(table) do
        if key == property then
          return true
        end
      end
      return false
    end
    
    say:set_namespace("en")
    say:set("assertion.has_property.positive", "Expected property %s in:\n%s")
    say:set("assertion.has_property.negative", "Expected property %s to not be in:\n%s")
    assert:register("assertion", "has_property", has_property, "assertion.has_property.positive", "assertion.has_property.negative")
    
    assert.has_property("name", { name = "jack" })
  6. Add and remove custom formatters

    master

    Formatters convert values into string representations for failure messages. A formatter is a function that takes a value and returns a formatted string, or nil if it cannot handle that type.

    • Add a formatter: assert:add_formatter(formatter_func)
    • Remove a formatter: assert:remove_formatter(formatter_func)

    Formatters added later take precedence over existing ones (e.g., a custom string formatter will override the default one).

    local binstring = require("luassert.formatters.binarystring")
    
    setup(function()
      assert:add_formatter(binstring)
    end)
    
    teardown(function()
      assert:remove_formatter(binstring)
    end)