A unit testing framework for Lean 4. LeanTest provides an expressive, easy-to-use testing interface for writing and running tests in Lean.
- Fluent API: Clean, readable test definition syntax using pipe operators
- Rich Assertions: Comprehensive set of assertion functions including:
- Basic assertions (equality, boolean, Option, collections)
- Range checking (
assertInRange) - Error handling (
assertError,assertOkforExcepttypes) - IO testing (
assertThrows,assertSucceedsfor IO operations)
- Colorized Output: Clear, colorful test results in the terminal
- Test Organization: Group tests into test suites for better organization
- Custom Messages: Add custom error messages to all assertions
- CI/CD Ready: Proper exit codes for continuous integration
Add LeanTest as a dependency to your project's lakefile.toml:
[[require]]
name = "LeanTest"
git = "https://github.com/tim-br/LeanTest"
rev = "main"Then run:
lake update
lake buildHere's a simple example:
import LeanTest
open LeanTest
/-- Define a test suite -/
def myTests : TestSuite :=
(TestSuite.empty "My First Tests")
|>.addTest "addition works" (do
return assertEqual 4 (2 + 2))
|>.addTest "strings concatenate" (do
return assertEqual "hello world" ("hello" ++ " " ++ "world"))
|>.addTest "boolean assertion" (do
return assertTrue true)
/-- Run the tests -/
def main : IO UInt32 := do
runTestSuitesWithExitCode [myTests]LeanTest provides the following assertion functions:
Assert that a boolean condition is true.
assert (5 > 3)
assert (x == y) "x and y should be equal"Assert that two values are equal.
assertEqual 10 (5 + 5)
assertEqual "hello" myString "Should greet with hello"Assert that two values are not equal.
assertNotEqual 5 10
assertNotEqual "foo" "bar"Refute that a condition is true (assert it's false).
refute (5 < 3)
refute isEmpty "List should not be empty"Assert that a boolean value is true or false.
assertTrue (isEven 4)
assertFalse (isOdd 4)Assert that an Option is Some or None.
assertSome (some 42)
assertNone (none : Option Nat)Assert that a list is empty.
assertEmpty ([] : List Nat)Assert that a list contains an element.
assertContains [1, 2, 3, 4] 3Assert that a value is within a range (inclusive).
assertInRange 5 1 10 -- checks if 5 is in [1, 10]
assertInRange 3.14 0.0 5.0 -- works with floats
assertInRange (-5) (-10) 0 -- works with negative rangesAssert the result of an Except type.
-- Assert that result is an error
let result : Except String Nat := .error "failed"
assertError result
-- Assert that result is ok
let result : Except String Nat := .ok 42
assertOk resultAssert whether an IO action throws an error. Note: These return IO AssertionResult.
-- Assert that IO action throws
assertThrows (IO.FS.readFile "/nonexistent/file.txt")
-- Assert that IO action succeeds
assertSucceeds (pure ())def myTestSuite : TestSuite :=
(TestSuite.empty "Suite Name")
|>.addTest "test description 1" (do
return assertEqual expected actual)
|>.addTest "test description 2" (do
return assertTrue condition)For CI/CD (recommended):
def main : IO UInt32 := do
-- Returns exit code: 0 if all tests pass, 1 if any fail
runTestSuitesWithExitCode [suite1, suite2, suite3]For interactive use:
def main : IO Unit := do
-- Always exits with code 0
runTestSuites [suite1, suite2, suite3]The examples/ directory contains several example test files:
Demonstrates basic assertions with arithmetic, boolean, and string tests.
Run it:
lake env lean examples/BasicTest.leanShows how to test lists, options, and custom data structures.
Run it:
lake env lean examples/CollectionTest.leanExamples of custom error messages and function testing.
Run it:
lake env lean examples/CustomMessageTest.leanReal-world example testing a complete Stack data structure implementation. Demonstrates:
- Testing stateful operations (push, pop, peek)
- Testing edge cases (empty stack, full sequences)
- Testing invariants (LIFO ordering, size consistency)
- Testing with different types (Nat, String)
- 22 comprehensive tests covering all operations
Run it:
lake env lean --run examples/StackTest.leanTests for advanced assertions including range checking, error handling, and IO operations. Demonstrates:
- Range assertions (
assertInRange) - Error handling with
Excepttypes (assertError,assertOk) - IO error testing (
assertThrows,assertSucceeds) - Real-world validation example (config parsing)
- 28 tests covering edge cases and error scenarios
Run it:
lake env lean --run examples/AdvancedAssertionsTest.leanLeanTest provides colorized terminal output:
Arithmetic Tests
✓ addition works correctly
✓ subtraction works correctly
✓ multiplication works correctly
Boolean Tests
✓ assert true condition
✗ this test failed
Expected: true
Actual: false
Test Summary:
Total: 5
Passed: 4
Failed: 1
FAILED
- Create a new
.leanfile in your project - Import LeanTest:
import LeanTest - Open the namespace:
open LeanTest - Define your test suites using
TestSuite.emptyand.addTest - Create a
mainfunction that callsrunTestSuitesWithExitCode(for CI) orrunTestSuites(for interactive use) - Run with:
lake env lean --run your_test_file.lean
When using runTestSuitesWithExitCode, your test executable will:
- Return exit code 0 if all tests pass
- Return exit code 1 if any tests fail
This makes it easy to integrate with CI/CD pipelines:
lake env lean --run tests/MyTests.lean
# Exit code will be non-zero if tests fail, failing the CI buildYou can test custom data types as long as they derive BEq and Repr:
structure Point where
x : Int
y : Int
deriving Repr, BEq
def pointTests : TestSuite :=
(TestSuite.empty "Point Tests")
|>.addTest "points are equal" (do
return assertEqual (Point.mk 1 2) (Point.mk 1 2))def fibonacci : Nat → Nat
| 0 => 0
| 1 => 1
| n + 2 => fibonacci n + fibonacci (n + 1)
def fibTests : TestSuite :=
(TestSuite.empty "Fibonacci Tests")
|>.addTest "fib(0) = 0" (do
return assertEqual 0 (fibonacci 0))
|>.addTest "fib(5) = 5" (do
return assertEqual 5 (fibonacci 5))To run all example tests at once:
./run_all_tests.shThis script:
- Builds the LeanTest framework
- Runs all example test suites (BasicTest, CollectionTest, CustomMessageTest, StackTest, AdvancedAssertionsTest)
- Reports a summary of passed/failed test suites
- Returns exit code 0 if all pass, 1 if any fail
LeanTest uses GitHub Actions to automatically run all tests on every push and pull request. The CI pipeline:
- Tests on both Ubuntu and macOS
- Verifies all examples pass
- Ensures the framework dogfoods its own testing capabilities
See .github/workflows/ci.yml for the full configuration.
Contributions are welcome! Feel free to:
- Add new assertion types
- Improve error messages
- Add more examples
- Enhance the test runner
Please ensure all tests pass before submitting a PR:
./run_all_tests.shApache License 2.0 - See LICENSE file for details