Testing
Write tests for your Lua on Beans application using Lester, a minimal testing framework for Lua.
Minimal Lua testing framework
Test controllers and routes
Test utilities and models
folder Test Structure
Tests are placed in the specs/ directory with _spec.lua suffix.
specs/
├── controllers/
│ └── welcome_page_spec.lua # Controller tests
├── models/
│ └── validations_spec.lua # Model tests
├── router_spec.lua # Routing tests
├── utilities_spec.lua # Utility function tests
└── arangodb_spec.lua # Database tests
code Basic Test Structure
Tests use Lester, a minimal testing framework. Each spec file returns a table with a run function.
Basic Spec File
specs/my_feature_spec.lua
-- Lester documentation: https://edubart.github.io/lester/
return {
run = function()
describe('my feature', function()
lester.before(function()
-- This runs before every test
end)
lester.after(function()
-- This runs after every test (cleanup)
end)
it('should do something', function()
expect.truthy(true)
end)
describe('nested context', function()
it('handles a specific case', function()
expect.equal(1 + 1, 2)
end)
end)
end)
end
}
checklist Assertions
Lester provides several assertion methods through the expect object.
expect.truthy(true)
expect.truthy("hello")
expect.truthy(1)
expect.truthy(user ~= nil)
expect.falsy(false)
expect.falsy(nil)
expect.falsy(user == nil)
expect.equal(status, 200)
expect.equal(user.name, "John")
expect.equal({ "a", "b" }, { "a", "b" })
expect.equal(#items, 3)
http Testing Controllers
Use Fetch() to make HTTP requests and test your controllers.
Controller Test Example
specs/controllers/welcome_page_spec.lua
return {
run = function()
describe('welcome controller', function()
describe('GET /', function()
it('returns 200 status', function()
local status, headers, body = Fetch("http://localhost:8080")
expect.equal(status, 200)
end)
it('contains welcome message', function()
local status, headers, body = Fetch("http://localhost:8080")
expect.truthy(string.match(body, "Welcome"))
end)
end)
describe('GET /about', function()
it('returns 200 status', function()
local status, headers, body = Fetch("http://localhost:8080/about")
expect.equal(status, 200)
end)
end)
end)
end
}
info Fetch() returns:
status — HTTP status code (200, 404, 500, etc.)
headers — Response headers table
body — Response body string
functions Testing Utilities
Test helper functions and utility methods.
return {
run = function()
describe('utilities', function()
it('table.keys returns all keys', function()
local tab = { name = "John", age = 30 }
local keys = table.keys(tab)
expect.equal(#keys, 2)
end)
it('table.contains checks membership', function()
expect.truthy(table.contains({ "a", "b", "c" }, "b"))
expect.falsy(table.contains({ "a", "b", "c" }, "d"))
end)
it('table.merge combines tables', function()
local merged = table.merge(
{ name = "John" },
{ age = 30 }
)
expect.equal(merged.name, "John")
expect.equal(merged.age, 30)
end)
it('string.split divides strings', function()
local parts = string.split("a,b,c", ",")
expect.equal(#parts, 3)
expect.equal(parts[1], "a")
end)
it('Slugify creates URL-friendly strings', function()
expect.equal(Slugify("Hello World"), "hello-world")
expect.equal(Slugify("Hello World!!"), "hello-world")
end)
end)
end
}
storage Testing Models & Database
Test model validations and database operations.
local model = require("arango_model")
local Customer = setmetatable({}, { __index = model })
Customer.__index = Customer
Customer.COLLECTION = "customers"
return {
run = function()
describe('Customer model', function()
-- Cleanup after tests
lester.after(function()
Adb.primary:CreateCollection("customers")
Adb.primary:DeleteCollection("customers")
end)
describe('validations', function()
it('validates presence', function()
function Customer.new(data)
local self = setmetatable(model.new(data), Customer)
self.validations = {
name = { presence = { message = "Name is required" } }
}
return self
end
local customer = Customer.new():create({ name = "John" })
expect.truthy(#customer.errors == 0)
local invalid = Customer.new():create({})
expect.truthy(#invalid.errors == 1)
expect.equal(invalid.errors[1].field, "name")
end)
it('validates numericality', function()
function Customer.new(data)
local self = setmetatable(model.new(data), Customer)
self.validations = {
age = { numericality = { only_integer = true } }
}
return self
end
local valid = Customer.new():create({ age = 25 })
expect.truthy(#valid.errors == 0)
local invalid = Customer.new():create({ age = 25.5 })
expect.truthy(#invalid.errors == 1)
end)
end)
end)
end
}
play_arrow Running Tests
Execute your test suite with the beans CLI.
# Run all specs
$ beans specs
# The server must be running for HTTP tests
# In another terminal:
$ beans dev
Testing Tips
Cleanup: Use lester.after() to clean up database records after tests to avoid pollution between runs.
Isolation: Each test should be independent. Don't rely on the order of test execution.
Descriptive names: Use clear describe() and it() messages that explain what's being tested.
HTTP tests: Make sure the development server is running before executing controller tests that use Fetch().