Lust is a small library that tests Lua code. It's useful because it is a single file and isn't very opinionated, which can make it easier to quickly add testing to a Lua project.
It supports nested describe/it blocks, before/after handlers, expect-style assertions, function spies, and colors.
Copy the lust.lua
file to a project directory and require it, which returns a table that includes all of the functionality.
local lust = require 'lust'
local describe, it, expect = lust.describe, lust.it, lust.expect
describe('my project', function()
lust.before(function()
-- This gets run before every test.
end)
describe('module1', function() -- Can be nested
it('feature1', function()
expect(1).to.be.a('number') -- Pass
expect('astring').to.equal('astring') -- Pass
end)
it('feature2', function()
expect(nil).to.exist() -- Fail
end)
end)
end)
Used to declare a group of tests. name
is a string used to describe the group, and func
is a function containing all tests and describe
blocks in the group. Groups created using describe
can be nested.
Used to declare a test, which consists of a set of assertions. name
is a string used to describe the test, and func
is a function containing the assertions.
Lust uses "expect style" assertions. An assertion begins with lust.expect(value)
and other modifiers can be chained after that:
Fails only if x
is nil
.
Performs a strict equality test, failing if x
and y
have different types or values. Tables are tested by recursively ensuring that both tables contain the same set of keys and values. Metatables are not taken into consideration.
Performs an equality test using the ==
operator. Fails if x ~= y
.
Fails if x
is nil
or false
.
If y
is a string, fails if type(x)
is not equal to y
. If y
is a table, walks up x
's metatable chain and fails if y
is not encountered.
If x
is a table, ensures that at least one of its keys contains the value y
using the ==
operator. If x
is not a table, this assertion fails.
Ensures that the function f
causes an error when it is run.
Fails if the string representation of x
does not match the pattern p
.
Negates the assertion.
Spies on a function and tracks the number of times it was called and the arguments it was called with. There are 3 ways to specify arguments to this function:
- Specify
nil
. - Specify a function.
- Specify a table and a name of a function in that table.
The return value is a table that will contain one element for each call to the function. Each element of this table is a table containing the arguments passed to that particular invocation of the function. The table can also be called as a function, in which case it will call the function it is spying on. The third argument, run
, is a function that will be called immediately upon creation of the spy. Example:
local object = {
method = function() end
}
-- Basic usage:
local spy = lust.spy(object, 'method')
object.method(3, 4) -- spy is now {{3, 4}}
object.method('foo') -- spy is now {{3, 4}, {'foo'}}
lust.expect(#spy).to.equal(2)
lust.expect(spy[1][2]).to.equal(4)
-- Using a run function:
local run = function()
object.method(1, 2, 3)
object.method(4, 5, 6)
end
lust.expect(lust.spy(object, 'method', run)).to.equal({{1, 2, 3}, {4, 5, 6}})
-- Using a function input:
local add = function(a, b)
return a + b
end
local spy = lust.spy(add)
spy(1, 2) -- => {{1, 2}}
spy('rain', 'bows') -- => {{1, 2}, {'rain', 'bows'}}
lust.expect(#spy).to.equal(2)
lust.expect(spy[2]).to.equal({'rain', 'bows'})
You can define functions that are called before and after every call to lust.it
using lust.before
and lust.after
. They are scoped to the describe
block that contains them as well as any inner describe
blocks.
Set a function that is called before every test inside this describe
block. fn
will be passed a single string containing the name of the test about to be run.
Set a function that is called after every test inside this describe
block. fn
will be passed a single string containing the name of the test that was finished.
Example of adding a custom empty
assertion:
local lust = require 'lust'
lust.paths.empty = {
test = function(value)
return #value == 0,
'expected ' .. tostring(value) .. ' to be empty',
'expected ' .. tostring(value) .. ' to not be empty'
end
}
table.insert(lust.paths.be, 'empty')
lust.expect({}).to.be.empty()
lust.expect('').to.be.empty()
First we define the assertion in the lust.paths
table. Each path is a table containing a test
function which performs the assertion. It returns three values: the result of the test (true for
pass, false for fail), followed by two messages: the first for a normal expectation failure, the
second for when the expectation is negated.
We then insert our 'empty' assertion into the be
path -- the numeric keys of a path represent the
possible expectations that can be chained.
If Lua is embedded in an application and does not run in a console environment that understands ANSI color escapes, the library can be required as follows:
local lust = require('lust').nocolor()
MIT, see LICENSE for details.