https://github.com/llbbl/lisunit
A small unit-testing library for the Lisette programming language, in the spirit of JUnit / pytest.
https://github.com/llbbl/lisunit
Last synced: 24 days ago
JSON representation
A small unit-testing library for the Lisette programming language, in the spirit of JUnit / pytest.
- Host: GitHub
- URL: https://github.com/llbbl/lisunit
- Owner: llbbl
- License: mit
- Created: 2026-04-06T15:04:20.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-06T15:04:34.000Z (3 months ago)
- Last Synced: 2026-05-19T10:16:52.441Z (about 1 month ago)
- Size: 7.81 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# lisunit
A small unit-testing library for the [Lisette](https://lisette.run) programming language, in the spirit of JUnit / PHPUnit / pytest.
> **Status:** experimental. Tested against **lisette 0.1.2**.
## Why a library?
Lisette doesn't yet ship a built-in test runner (it's on the [roadmap](https://github.com/ivov/lisette/blob/main/docs/intro/roadmap.md)), and the language has no macros or attributes today for test discovery. `lisunit` takes the library-based approach — you register test cases as closures on a `Suite` and call `run()`.
When Lisette eventually ships a core `lis test` command, the runner/discovery parts of this library will likely become obsolete — but the assertion and matcher helpers should remain useful on top of it.
## Install
Lisette does not have a package manager yet, so installation is copy-paste:
1. Copy the `src/lisunit/` directory from this repo into your project's `src/` directory.
2. Add `import "lisunit"` in files that need it.
That's it. When Lisette gets a package manager, this README will be updated with the proper dependency declaration.
## Quickstart
```lis
import "lisunit"
fn add(a: int, b: int) -> int {
a + b
}
fn main() {
lisunit.Suite.new("math")
.case("add produces sum", || {
lisunit.assert_eq_int(add(2, 3), 5)?
Ok(())
})
.case("add is commutative", || {
lisunit.assert_eq_int(add(2, 3), add(3, 2))?
Ok(())
})
.run()
}
```
Running with `lis run` will produce colored output and exit non-zero if any tests fail.
## API
### `Suite`
Builder for a named collection of test cases.
```lis
lisunit.Suite.new(name: string) -> Suite
.case(name: string, body: fn() -> Result<(), string>) -> Suite
.run() // prints results, exits non-zero on failure
.run_silent() -> SuiteResult // returns result without printing (for programmatic use)
```
### `run_all`
Run multiple suites in order with a combined summary.
```lis
lisunit.run_all(suites: Slice)
```
### Assertions
All assertions return `Result<(), string>` so they compose with the `?` operator.
```lis
lisunit.assert_true(cond: bool, msg: string) -> Result<(), string>
lisunit.assert_false(cond: bool, msg: string) -> Result<(), string>
lisunit.assert_eq_int(actual: int, expected: int) -> Result<(), string>
lisunit.assert_eq_string(actual: string, expected: string) -> Result<(), string>
lisunit.assert_eq_bool(actual: bool, expected: bool) -> Result<(), string>
lisunit.fail(msg: string) -> Result<(), string>
```
A test case body returns `Result<(), string>`. Return `Ok(())` to pass, `Err(msg)` to fail — or just use `?` on assertions and let them propagate:
```lis
.case("a complex test", || {
let result = do_something()
lisunit.assert_true(result.is_valid(), "should be valid")?
lisunit.assert_eq_int(result.count, 3)?
Ok(())
})
```
## Running the self-demo
This repo is itself a runnable Lisette project that demonstrates the library testing a few trivial functions:
```bash
git clone https://github.com/llbbl/lisunit
cd lisunit
lis run
```
Expected output: 8 green checks across two suites.
## Design notes
- **Manual registration.** Lisette has no `#[test]` attribute or reflection; every test case is registered explicitly via `.case(...)`. This is closer to JUnit/Jest/PHPUnit than to Rust/Go's built-in test runners.
- **`Result<(), string>` as the test return type.** Asserts return `Result`, so `?` propagates the first failure to the runner, which catches it via `match` to isolate failures between tests.
- **Test isolation via `match`.** The runner's per-case loop uses a `match` (not `?`) so one failing test does not stop subsequent tests in the suite.
- **Colored output via ANSI escapes.** Go's stdlib has no color helper, so `lisunit` hard-codes a small set of ANSI codes. If your terminal doesn't support them, they'll render as literal escape sequences.
- **No panic recovery yet.** A panic inside a test body (e.g. an unwrap of `None`) will currently kill the whole suite. Future versions may use Go's `recover()` if/when Lisette exposes it.
- **No `main`-free test discovery.** Because there's no `lis test` command, your `main()` must call the runner. A dedicated test-only binary isn't possible today.
## Roadmap
Likely short-term additions:
- More assertions: `assert_contains`, `assert_empty`, `assert_ok` / `assert_err` on `Result`
- Setup / teardown hooks (`before_each`, `after_each`)
- Name filtering: `lis run -- --filter "arithmetic"` runs only matching cases
- Per-test progress output (print each PASS/FAIL as it happens)
- Panic recovery once the mechanism is clear
When Lisette's core `lis test` ships, `lisunit` will pivot to being a higher-level matcher/helper library layered on top, similar to how Hamcrest layers on JUnit.
## Contributing
Issues and PRs welcome. Please file suggestions for assertions you'd like to see, or design thoughts for when this library and a future core `lis test` command should meet.
## License
MIT — see [LICENSE](LICENSE).