• Stars
    star
    202
  • Rank 193,691 (Top 4 %)
  • Language
    Lua
  • License
    MIT License
  • Created about 12 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Assertion library for Lua

Luassert

Busted Luacheck GitHub tag (latest SemVer) Luarocks

luassert extends Lua's built-in assertions to provide additional tests and the ability to create your own. You can modify chains of assertions with not.

Check out busted for extended examples.

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)

Extend your own:

local assert = require("luassert")
local say    = require("say") --our i18n lib, installed through luarocks, included as a luassert dependency

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" })

When writing your own assertions you can also use modifiers to set specific objects to work against. An example is the array modifier with its accompanying holes assertion.

Which can be used as;

local arr = { "one", "two", "three" }

assert.array(arr).has.no.holes()   -- checks the array to not contain holes --> passes
assert.array(arr).has.no.holes(4)  -- sets explicit length to 4 --> fails

Implementation notes:

  • assertion/modifiers that are Lua keywords (true, false, nil, function, and not) cannot be used using '.' chaining because that results in compilation errors. Instead chain using '_' (underscore) or use one or more capitals in the reserved word (see code examples above), whatever your coding style prefers
  • Most assertions will only take 1 or 2 parameters and an optional failure message, except for the returned_arguments assertion, which does not take a failure message
  • To specify a custom failure message for the returned_arguments assertion, use the message modifier
local f = function() end
assert.message("the function 'f' did not return 2 arguments").returned_arguments(2, f())

Matchers

Argument matching can be performed on spies/stubs with the ability to create your own. This provides flexible argument matching for called_with and returned_with assertions. Like assertions, you can modify chains of matchers with not. Furthermore, matchers can be combined using composite matchers.

local assert = require 'luassert'
local match = require 'luassert.match'
local spy = require 'luassert.spy'

local s = spy.new(function() end)
s('foo')
s(1)
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.is_number()) -- arg1 is a number
assert.spy(s).was.called_with(match.is_not_true()) -- arg1 is not true
assert.spy(s).was.called_with(match.is_table(), match.is_string()) -- arg1 is a table, arg2 is a string
assert.spy(s).was.called_with(match.has_match('.oo')) -- arg1 contains pattern ".oo"
assert.spy(s).was.called_with({}, 'foo') -- you can still match without using matchers

Extend your own:

local function is_even(state, arguments)
  return function(value)
    return (value % 2) == 0
  end
end

local function is_gt(state, arguments)
  local expected = arguments[1]
  return function(value)
    return value > expected
  end
end

assert:register("matcher", "even", is_even)
assert:register("matcher", "gt", is_gt)
local assert = require 'luassert'
local match = require 'luassert.match'
local spy = require 'luassert.spy'

local s = spy.new(function() end)
s(7)
assert.spy(s).was.called_with(match.is_number()) -- arg1 was a number
assert.spy(s).was.called_with(match.is_not_even()) -- arg1 was not even
assert.spy(s).was.called_with(match.is_gt(5)) -- arg1 was greater than 5

Composite matchers have the form:

match.all_of(m1, m2, ...) -- argument matches all of the matchers m1 to mn
match.any_of(m1, m2, ...) -- argument matches at least one of the matchers m1 to mn
match.none_of(m1, m2, ...) -- argument does not match any of the matchers m1 to mn

If you're creating a spy for methods that mutate any properties on self you should should use match.is_ref(obj):

local t = { cnt = 0, }
function t:incrby(i) self.cnt = self.cnt + i end

local s = spy.on(t, "incrby")
t:incrby(2)

assert.spy(s).was_called_with(match.is_ref(t), 2)

Snapshots

To be able to revert changes created by tests, inserting spies and stubs for example, luassert supports 'snapshots'. A snapshot includes the following;

  1. spies and stubs
  2. parameters
  3. formatters

Example:

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)

Parameters

To register state information 'parameters' can be used. The parameter is included in a snapshot and can hence be restored in between tests. For an example see Configuring table depth display below.

Example:

assert:set_parameter("my_param_name", 1)
local s = assert:snapshot()
assert:set_parameter("my_param_name", 2)
s:revert()
assert.are.equal(1, assert:get_parameter("my_param_name"))

Customizing argument formatting

luassert comes preloaded with argument formatters for common Lua types, but it is easy to roll your own. Customizing them is especially useful for limiting table depth and for userdata types.

Configuring table depth display

The default table formatter allows you to customize the levels displayed by setting the TableFormatLevel parameter (setting it to -1 displays all levels).

Example:

describe("Tests different levels of table display", function()

  local testtable = {
      hello = "hola",
      world = "mundo",
      liqour = {
          "beer", "wine", "water"
        },
      fruit = {
          native = { "apple", "strawberry", "grape" },
          tropical = { "banana", "orange", "mango" },
        },
    }

  it("tests display of 0 levels", function()
    assert:set_parameter("TableFormatLevel", 0)
    assert.are.same(testtable, {})
  end)

  it("tests display of 2 levels", function()
    assert:set_parameter("TableFormatLevel", 2)
    assert.are.same(testtable, {})
  end)

end)

Will display the following output with the table pretty-printed to the requested depth:

Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 45
tests display of 0 levels
...ua projects\busted\formatter\spec\formatter_spec.lua:47: Expected objects to be the same. Passed in:
(table): { }
Expected:
(table): { ... more }

Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 50
tests display of 2 levels
...ua projects\busted\formatter\spec\formatter_spec.lua:52: Expected objects to be the same. Passed in:
(table): { }
Expected:
(table): {
  [hello] = 'hola'
  [fruit] = {
    [tropical] = { ... more }
    [native] = { ... more } }
  [liqour] = {
    [1] = 'beer'
    [2] = 'wine'
    [3] = 'water' }
  [world] = 'mundo' }

Customized formatters

The formatters are functions taking a single argument that needs to be converted to a string representation. The formatter should examine the value provided, if it can format the value, it should return the formatted string, otherwise it should return nil. Formatters can be added through assert:add_formatter(formatter_func), and removed by calling assert:remove_formatter(formatter_func).

Example using the included binary string formatter:

local binstring = require("luassert.formatters.binarystring")

describe("Tests using a binary string formatter", function()

  setup(function()
    assert:add_formatter(binstring)
  end)

  teardown(function()
    assert:remove_formatter(binstring)
  end)

  it("tests a string comparison with binary formatting", function()
    local s1, s2 = "", ""
    for n = 65,88 do
      s1 = s1 .. string.char(n)
      s2 = string.char(n) .. s2
    end
    assert.are.same(s1, s2)

  end)

end)

Because this formatter formats string values, and is added last, it will take precedence over the regular string formatter. The results will be:

Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 13
tests a string comparison with binary formatting
...ua projects\busted\formatter\spec\formatter_spec.lua:19: Expected objects to be the same. Passed in:
Binary string length; 24 bytes
58 57 56 55 54 53 52 51   50 4f 4e 4d 4c 4b 4a 49  XWVUTSRQ PONMLKJI
48 47 46 45 44 43 42 41                            HGFEDCBA

Expected:
Binary string length; 24 bytes
41 42 43 44 45 46 47 48   49 4a 4b 4c 4d 4e 4f 50  ABCDEFGH IJKLMNOP
51 52 53 54 55 56 57 58                            QRSTUVWX

More Repositories

1

Penlight

A set of pure Lua libraries focusing on input data handling (such as reading configuration files), functional programming (such as map, reduce, placeholder expressions,etc), and OS path management. Much of the functionality is inspired by the Python standard libraries.
Lua
1,883
star
2

luasocket

Network support for the Lua language
HTML
1,814
star
3

busted

Elegant Lua unit testing.
Lua
1,387
star
4

luafilesystem

LuaFileSystem is a Lua library developed to complement the set of functions related to file systems offered by the standard Lua distribution.
C
891
star
5

ldoc

LDoc is a LuaDoc-compatible documentation generator which can also process C extension source. Markdown may be optionally used to render comments, as well as integrated readme documentation and pretty-printed example files.
Lua
774
star
6

luasql

LuaSQL is a simple interface from Lua to a DBMS.
Lua
536
star
7

luasec

LuaSec
C
364
star
8

luacov

LuaCov is a simple coverage analyzer for Lua code.
Lua
295
star
9

lua-compat-5.3

Compatibility module providing Lua-5.3-style APIs for Lua 5.2 and 5.1
C
225
star
10

copas

Copas is a dispatcher based on coroutines that can be used by TCP/IP servers.
Lua
202
star
11

lua_cliargs

A command-line argument parsing module for Lua.
Lua
120
star
12

md5

MD5 offers basic cryptographic facilities for Lua 5.1.
C
109
star
13

cgilua

CGILua is a tool for creating dynamic HTML pages and manipulating input data from Web forms.
Lua
93
star
14

lua-iconv

Lua bindings for POSIX iconv
Lua
90
star
15

lualogging

A simple API to use logging features in Lua
Lua
51
star
16

say

Lua string hashing library, useful for internationalization
Lua
45
star
17

luaexpat

LuaExpat is a SAX XML parser based on the Expat library
Lua
23
star
18

luasystem

Platform independent system calls for Lua
C
19
star
19

lua-mimetypes

fixed repo of https://luarocks.org/modules/luarocks/mimetypes
Lua
6
star
20

luasyslog

Addon for lualogging to log to the system log on unix systems
Lua
6
star
21

moon-sand

Sandbox to mess with CIβ€Œ deployments using LunarModules CI key(s)
Lua
2
star
22

expadom

An XML DOM Level 2 Core implementation in Lua, based on the (Lua)Expat parser.
Lua
2
star
23

lunarmodules

providing the profile page (README.md) for the Lunar Modules organization
2
star
24

.github

Lunarmodules profile page and reusable workflows
2
star
25

lua-lace

Lua Access Control Engine
Lua
2
star