Skip to main content

run_tests

Function run_tests 

Source
pub fn run_tests(code: &str, chunk_name: &str) -> Result<TestSummary, String>
Expand description

Run Lua test code with the lust framework pre-loaded.

Creates a fresh Lua VM, registers lust and test doubles, executes code, and returns the structured test summary.

Lua’s print is replaced with a no-op to prevent stdout pollution. Callers who need console output should use register on their own Lua instance where print remains intact.

Examples found in repository?
examples/basic_bdd.rs (lines 7-50)
6fn main() {
7    let summary = mlua_lspec::run_tests(
8        r#"
9        local describe, it, expect = lust.describe, lust.it, lust.expect
10
11        describe('string utilities', function()
12            describe('upper', function()
13                it('converts lowercase to uppercase', function()
14                    expect(string.upper("hello")).to.equal("HELLO")
15                end)
16
17                it('leaves uppercase unchanged', function()
18                    expect(string.upper("HELLO")).to.equal("HELLO")
19                end)
20
21                it('handles empty string', function()
22                    expect(string.upper("")).to.equal("")
23                end)
24            end)
25
26            describe('rep', function()
27                it('repeats string n times', function()
28                    expect(string.rep("ab", 3)).to.equal("ababab")
29                end)
30
31                it('returns empty for zero repeats', function()
32                    expect(string.rep("x", 0)).to.equal("")
33                end)
34            end)
35
36            describe('find', function()
37                it('returns start index', function()
38                    local start = string.find("hello world", "world")
39                    expect(start).to.equal(7)
40                end)
41
42                it('returns nil for no match', function()
43                    local result = string.find("hello", "xyz")
44                    expect(result).to_not.exist()
45                end)
46            end)
47        end)
48    "#,
49        "@basic_bdd.lua",
50    )
51    .expect("test execution failed");
52
53    println!(
54        "Results: {} passed, {} failed",
55        summary.passed, summary.failed
56    );
57    for test in &summary.tests {
58        let icon = if test.passed { "PASS" } else { "FAIL" };
59        println!("  [{icon}] {}: {}", test.suite, test.name);
60        if let Some(ref err) = test.error {
61            println!("         {err}");
62        }
63    }
64
65    assert_eq!(summary.failed, 0, "all tests should pass");
66}
More examples
Hide additional examples
examples/test_doubles_usage.rs (lines 7-187)
6fn main() {
7    let summary = mlua_lspec::run_tests(
8        r#"
9        local describe, it, expect = lust.describe, lust.it, lust.expect
10
11        -- ================================================================
12        -- System under test: an event dispatcher
13        -- ================================================================
14        local function create_dispatcher()
15            local handlers = {}
16            return {
17                on = function(self, event, handler)
18                    handlers[event] = handlers[event] or {}
19                    table.insert(handlers[event], handler)
20                end,
21                emit = function(self, event, data)
22                    if handlers[event] then
23                        for _, h in ipairs(handlers[event]) do
24                            h(data)
25                        end
26                    end
27                end,
28                handler_count = function(self, event)
29                    return handlers[event] and #handlers[event] or 0
30                end,
31            }
32        end
33
34        -- ================================================================
35        -- Tests
36        -- ================================================================
37
38        describe('event dispatcher', function()
39
40            describe('spy: verifying handler calls', function()
41                it('calls registered handler on emit', function()
42                    local d = create_dispatcher()
43                    local handler = test_doubles.spy(function() end)
44
45                    d:on("click", handler)
46                    d:emit("click", { x = 10, y = 20 })
47
48                    expect(handler:call_count()).to.equal(1)
49                end)
50
51                it('passes event data to handler', function()
52                    local d = create_dispatcher()
53                    local handler = test_doubles.spy(function() end)
54
55                    d:on("click", handler)
56                    d:emit("click", { x = 10, y = 20 })
57
58                    -- was_called_with compares primitives; for tables, use call_args
59                    local args = handler:call_args(1)
60                    expect(args[1].x).to.equal(10)
61                    expect(args[1].y).to.equal(20)
62                end)
63
64                it('calls multiple handlers in order', function()
65                    local d = create_dispatcher()
66                    local order = {}
67                    local h1 = test_doubles.spy(function() table.insert(order, "first") end)
68                    local h2 = test_doubles.spy(function() table.insert(order, "second") end)
69
70                    d:on("click", h1)
71                    d:on("click", h2)
72                    d:emit("click", {})
73
74                    expect(h1:call_count()).to.equal(1)
75                    expect(h2:call_count()).to.equal(1)
76                    expect(order).to.equal({"first", "second"})
77                end)
78
79                it('does not call handler for different event', function()
80                    local d = create_dispatcher()
81                    local handler = test_doubles.spy(function() end)
82
83                    d:on("click", handler)
84                    d:emit("hover", {})
85
86                    expect(handler:call_count()).to.equal(0)
87                end)
88            end)
89
90            describe('stub: faking external services', function()
91                -- Simulate a module that calls an external API
92                local function fetch_user(api_client, user_id)
93                    local response = api_client.get("/users/" .. user_id)
94                    if response.status == 200 then
95                        return response.body
96                    end
97                    return nil, "not found"
98                end
99
100                it('returns user data on success', function()
101                    local api = { get = test_doubles.stub() }
102                    api.get:returns({ status = 200, body = { name = "Alice", id = 1 } })
103
104                    local user = fetch_user(api, 1)
105
106                    expect(user).to.exist()
107                    expect(user.name).to.equal("Alice")
108                    expect(api.get:call_count()).to.equal(1)
109                end)
110
111                it('returns nil on not found', function()
112                    local api = { get = test_doubles.stub() }
113                    api.get:returns({ status = 404, body = nil })
114
115                    local user, err = fetch_user(api, 999)
116
117                    expect(user).to_not.exist()
118                    expect(err).to.equal("not found")
119                end)
120            end)
121
122            describe('spy_on: intercepting existing methods', function()
123                it('records calls to existing method', function()
124                    local logger = {
125                        messages = {},
126                        log = function(self, msg)
127                            table.insert(self.messages, msg)
128                        end,
129                    }
130
131                    local spy = test_doubles.spy_on(logger, "log")
132
133                    -- Call through the original object
134                    logger.log(logger, "hello")
135                    logger.log(logger, "world")
136
137                    -- Spy recorded the calls
138                    expect(spy:call_count()).to.equal(2)
139
140                    -- Original still worked (call-through)
141                    expect(#logger.messages).to.equal(2)
142                end)
143
144                it('reverts to original after test', function()
145                    local calculator = {
146                        add = function(a, b) return a + b end,
147                    }
148
149                    local spy = test_doubles.spy_on(calculator, "add")
150                    calculator.add(1, 2)  -- recorded by spy
151                    expect(spy:call_count()).to.equal(1)
152
153                    spy:revert()
154
155                    -- Now it's the original function again
156                    local result = calculator.add(10, 20)
157                    expect(result).to.equal(30)
158                end)
159            end)
160
161            describe('spy as stub: switching behavior', function()
162                it('spy can be converted to stub mid-test', function()
163                    local counter = 0
164                    local s = test_doubles.spy(function()
165                        counter = counter + 1
166                        return counter
167                    end)
168
169                    -- Initially calls through
170                    expect(s()).to.equal(1)
171                    expect(s()).to.equal(2)
172
173                    -- Switch to stub behavior
174                    s:returns(999)
175
176                    -- Now returns fixed value
177                    expect(s()).to.equal(999)
178                    expect(s()).to.equal(999)
179
180                    -- All 4 calls recorded
181                    expect(s:call_count()).to.equal(4)
182                end)
183            end)
184        end)
185    "#,
186        "@test_doubles.lua",
187    )
188    .expect("test execution failed");
189
190    println!(
191        "{} passed, {} failed out of {} tests",
192        summary.passed, summary.failed, summary.total
193    );
194    for test in &summary.tests {
195        let icon = if test.passed { "PASS" } else { "FAIL" };
196        println!("  [{icon}] {}: {}", test.suite, test.name);
197        if let Some(ref err) = test.error {
198            println!("         {err}");
199        }
200    }
201
202    assert_eq!(summary.failed, 0, "all tests should pass");
203}