Testing

Write tests for your Lua on Beans application using Lester, a minimal testing framework for Lua.

check_circle
Lester

Minimal Lua testing framework

http
HTTP Testing

Test controllers and routes

functions
Unit Tests

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.

description

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(value) Assert value is truthy
expect.truthy(true)
expect.truthy("hello")
expect.truthy(1)
expect.truthy(user ~= nil)
expect.falsy(value) Assert value is falsy
expect.falsy(false)
expect.falsy(nil)
expect.falsy(user == nil)
expect.equal(a, b) Assert values are equal
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.

web

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:

check_circle status — HTTP status code (200, 404, 500, etc.)
check_circle headers — Response headers table
check_circle 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
lightbulb

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().