Library / SDK
bluebird75/luaunit avatar
bluebird75/luaunit

LuaUnit packs a Lua test runner into one file, and the diff output is the reason to pick it

LuaUnit is a popular unit-testing framework for Lua, with an interface typical of xUnit libraries (Python unittest, Junit, NUnit, ...). It supports several output formats (Text, TAP, JUnit, ...) to be used directly or work with Continuous Integration platforms (Jenkins, Maven, ...).

636 stars142 forksLuaNOASSERTION

At a glance

What is it?
LuaUnit is a unit testing framework for Lua that follows xUnit conventions, ships as a single file with no external dependency, and can output TAP or JUnit XML for CI. Its list comparison analysis and assertion library mode are the parts other runners in the Lua ecosystem do not usually offer.
Who is it for?
LuaUnit fits teams writing Lua libraries who want xUnit-style assertions, a CI friendly XML or TAP report, and no dependency to vendor or pin. It does not fit projects that need a richer plugin model or a growing ecosystem of test tooling.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Lua, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One file, no dependency tree, and an xUnit shape

LuaUnit is a unit testing framework for Lua, written by Philippe Fremy, with an interface that follows the xUnit conventions familiar from Python unittest, JUnit, and NUnit. That resemblance is deliberate and practical. If your team already writes tests in one of those styles, the assertion names and the structure of a test file carry over without a learning tax.

The distribution shape is unusual and is the first thing to understand. LuaUnit is contained into a single file and has no external dependency. There is no plugin manager, no transitive Lua rock to resolve, and nothing to pin in a lockfile. You copy luaunit.lua next to your tests and require it.

The framework is not limited to test files. It may also be used as an assertion library to validate assertions inside a running program, which is a different job from testing: runtime validation of arguments and state inside application code. It also ships a pretty stringifier that converts any type into a formatted string, including complex nested or recursive tables. That stringifier is what keeps failure messages readable when the compared value is a table rather than a scalar, and it is the reason the framework can be used outside a test harness at all.

There is also dedicated support for scientific computing, described in the project documentation, for teams doing numeric work in Lua who need tolerances and floating point comparisons rather than exact equality.

The list difference analysis that tells you where two tables diverged

Since version 3.3, comparing two lists produces a detailed difference analysis instead of a bare mismatch message. The example in the project description is deliberately a puzzle: two long numeric tables that look identical at a glance. The test code is written like this:

lua
   -- lua test code. Can you spot the difference ?
   function TestListCompare:test1()
       local A = { 121221, 122211, 121221, 122211, 121221, 122212, 121212, 122112, 122121, 121212, 122121 } 
       local B = { 121221, 122211, 121221, 122211, 121221, 122212, 121212, 122112, 121221, 121212, 122121 }
       lu.assertEquals( A, B )
   end

Running it prints a report that walks through the structure of the comparison:

sh
   List difference analysis:
   * lists A (actual) and B (expected) have the same size
   * lists A and B start differing at index 9
   * lists A and B are equal again from index 10
   * Common parts:
     = A[1], B[1]: 121221
     = A[2], B[2]: 122211
     = A[3], B[3]: 121221
     = A[4], B[4]: 122211
     = A[5], B[5]: 121221
     = A[6], B[6]: 122212
     = A[7], B[7]: 121212
     = A[8], B[8]: 122112
   * Differing parts:
     - A[9]: 122121
     + B[9]: 121221
   * Common parts at the end of the lists
     = A[10], B[10]: 121212
     = A[11], B[11]: 122121

The value is not the failure flag, it is the shape of the report. It tells you the first divergent index, that the lists re-converge afterwards, and then separates common elements from the elements that actually differ. On a table of a few hundred entries, that is the difference between reading a failure and hunting for it.

Installing through LuaRocks or a plain git clone

There are two supported routes and neither involves a build step, because there is nothing to build.

The package manager route is LuaRocks. The project is published as a rock on luarocks.org under the module name bluebird75/luaunit, and the project pages state a floor for the tooling: you need at least LuaRocks version 2.4.4, because older versions of wget are incompatible with downloading over https from GitHub. That constraint is old news, but it still names a minimum version, which is a useful signal that the rock has been published for a long time and its installation path is stable.

The second route is a direct clone:

bash
   git clone [email protected]:bluebird75/luaunit.git

After cloning, you copy the file luaunit.lua into your project, or into the Lua libs directory if you are using regular Lua, or into the lua directory if you are using LuaJIT. That single-file layout is what makes the clone route viable at all. There is no tree of modules to add to your path.

The project pages state that the version of the main branch on GitHub is always stable and can be used safely, which removes the usual temptation to pin a tag. You do not need to look for an install script, a post-install hook, or a generated manifest. Copy one file, require it, and write a test file whose name and method names follow the expected conventions. The command line options then select which tests run, control the output format, and set verbosity.

TAP and JUnit XML decide how far your CI has to read

Output format is the point where a test framework either disappears into the pipeline or becomes a maintenance chore. LuaUnit supports several output formats, Text, TAP, and JUnit among them, and they can be used directly or fed to Continuous Integration platforms such as Jenkins, Hudson, and Maven.

That list of formats is a real difference in approach rather than a checkbox. A TAP stream is a line oriented protocol that any CI runner can consume without understanding XML. JUnit XML is the other direction: a structured report that CI dashboards can parse to attribute failures to individual test cases and to show history across runs. Text output is what you read while writing the test, at the terminal, in front of the person holding the stack trace.

The same runner therefore serves both ends of the loop. Locally, a developer runs a single file and reads the diff analysis. In CI, the whole suite runs and the report is emitted in a format the platform already understands. The repository layout backs this up, with a junitxml directory alongside the main luaunit.lua file, so XML emission has its own home in the source tree rather than being bolted on.

Selection is done from the command line, which the project describes as a flexible interface for choosing tests by name or by pattern, controlling the output format, and setting verbosity. The patterns matter in practice, because a large Lua codebase rarely wants every test file executed on every save.

Version 3.6 changes naming conventions and suite level hooks

The change log for the version in development shows where the framework is heading. Two items stand out for adopters.

The first is flexibility in how tests are detected. The name convention used to detect tests and test methods can now be adjusted with the options --test-prefix, --test-suffix, and --method-prefix. Until now the naming rules were fixed, which is fine in a greenfield repository and annoying in an existing codebase where your test files or methods already carry a house style. With these options, a team can keep its own prefix and still let the runner find the tests. Note that only three of the options appear here, so the suffix and method suffix conventions are not all adjustable in the same way.

The second item is documentation for setupSuite(), teardownSuite(), setupClass(), and teardownClass(). These are the hooks for fixtures that span more than one test, and having them written down matters more than it sounds: without knowing which scope each hook belongs to, it is easy to put expensive setup in the wrong place and pay for it on every test method.

Version 3.5, released on 26 March 2026, added support for Lua 5.5, the ability to pass command-line arguments to runSuitByInstances(), and a bugfix in which LuaUnit correctly escapes invalid XML characters when outputting XML. That last one is worth noting if you push test output into a CI dashboard, since an unescaped character in a failure message can break the XML report rather than merely look wrong.

What the project does not settle for you

Two gaps are visible in what the project pages describe.

The first is the licence. The README states that LuaUnit is released under the BSD license, while the licence field for the repository is not a standard SPDX identifier that GitHub can classify, and a LICENSE.txt file sits at the top level of the tree. Those two statements are not necessarily in conflict, but they are not the same statement either. If your organisation runs a licence scanner over dependencies, check the text of LICENSE.txt against your policy rather than trusting either label.

The second is the boundary of the framework itself. LuaUnit gives you assertions, output formats, a CLI, an assertion library mode, and a stringifier. It does not describe a plugin or extension ecosystem, a mocking layer, a fixture language beyond the four hooks, or integration with any continuous integration system beyond emitting a report it understands. If your workflow depends on rich test doubles, generated mocks, or per-project test templates, you will be building that layer yourself on top of the single file.

There is a third, smaller limit worth stating. The project lists its own successes, naming Slurm and a fork of LuaUnit used at CERN, but these are cases where LuaUnit was one component of a much larger system. They show the framework runs in demanding environments, not that it covers every need those projects had.

Testing practice and the pace of releases

The project's own claim about its test coverage is a coverage figure of 99.5%, and the test suite is run on every version of Lua, from Lua 5.1 through 5.5, plus LuaJIT 2.0, and on several operating systems including Windows Server, MacOS X, and Ubuntu. The repository layout backs that up with run_unit_tests.lua and run_functional_tests.lua at the top level, plus a doit.py driver and a test directory.

Release cadence tells a different story from commit activity. The last push to the repository was on 2026-10-20, which is recent, but tagged releases are sparse: version 3.4 dates from 2021-03-04, version 3.5 from 2026-03-26, and version 3.3 from 2018-03-17. So the project is alive and shipping, yet a version gap of five years sits between 3.4 and 3.5. If you are the kind of team that wants a new tagged release for every change, that history is a poor fit. If you want a maintained framework whose main branch is stable, the same history is fine.

For reference, the project states that version 3.4 was downloaded more than 270 000 times on LuaRocks. That figure describes reach, not quality, but it does indicate the rock has been in continuous circulation for years.

Editorial conclusion

LuaUnit fits teams writing Lua libraries who want xUnit-style assertions, a CI friendly XML or TAP report, and no dependency to vendor or pin. It does not fit projects that need a richer plugin model or a growing ecosystem of test tooling. Before adopting it, check that your Lua runtime is covered, since the test suite runs on Lua 5.1 through 5.5 and LuaJIT 2.0, and confirm the licence position yourself, because the README describes a BSD release while the repository licence field points to a custom LICENSE.txt.

Frequently asked questions

How do I install LuaUnit for a Lua project?

You can install it as a LuaRocks module, which requires at least LuaRocks version 2.4.4, or clone the GitHub repository and copy the single file luaunit.lua into your project, the Lua libs directory, or the lua directory for LuaJIT.

Which output formats can LuaUnit produce for CI?

LuaUnit supports several output formats including Text, TAP, and JUnit, and they can be used directly or with Continuous Integration platforms such as Jenkins, Hudson, and Maven.

Can LuaUnit be used outside of a test suite?

Yes. It can be used as an assertion library to validate assertions inside a running program, and it ships a pretty stringifier that formats any type, including complex nested or recursive tables.

How do I change the naming convention LuaUnit uses to find tests?

In version 3.6, currently in development, you will be able to adjust the detection conventions with the options --test-prefix, --test-suffix, and --method-prefix.

Which Lua versions does LuaUnit support?

The test suite runs on every version of Lua from 5.1 to 5.5 and on LuaJIT 2.0, and version 3.5 added support for Lua 5.5.

Official sources

  1. bluebird75/luaunit on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bluebird75-luaunit.svg)](https://hysenlabs.com/projects/bluebird75-luaunit)

Was this useful?