# Testing
Deft ships a built-in test framework: `defsuite` groups tests,
`deftest` defines individual cases, and the `assert/` namespace checks
outcomes. The `deft test` command runs `.dft` files matching
`test_*.dft` or explicitly named files.
## Hello, Test
defsuite "arithmetic" { deftest "addition" { [assert/eq (1 + 2) 3] } deftest "multiplication" { [assert/eq (2 * 3) 6] } }
Run with:
```
deft test test_math.dft
```
## Assertions
Each assertion is its own stdlib function under the `assert/` namespace —
no kind keyword, just the slash-separated name:
[assert/eq $a $b] # equality [assert/ne $a $b] # inequality [assert/truthy $x] # truthy [assert/falsy $x] # falsy [assert/gt $a $b] # greater than [assert/gte $a $b] # greater than or equal [assert/lt $a $b] # less than [assert/lte $a $b] # less than or equal [assert/nil? $x] # nil check [assert/not-nil? $x] # not-nil check [assert/empty? $xs] # empty collection [assert/not-empty? $xs] # non-empty collection [assert/contains? $xs $v] # collection contains value [assert/type $x "number"] # type check [assert/length $xs $n] # length check
For predicates that already end in `?`, use the matching `assert/`
form directly:
[assert/nil? $result] [assert/truthy [empty? $xs]]
### Custom assertion messages
The optional last argument is a failure message:
[assert/eq $status 200 "expected HTTP 200"]
## Test Discovery
`deft test` with no arguments looks for `test/test_*.dft` relative to
the current directory. Pass paths explicitly to run a subset:
```
deft test # all tests under ./test/
deft test test_sqlite.dft # one file
deft test test/test_sqlite.dft test/test_atoms.dft # several
```
## Writing Good Tests
### Use type hints on the system under test
def add {|a^number b^number| ($a + $b) } defsuite "add" { deftest "positive" { [assert/eq [add 1 2] 3] } deftest "negative" { [assert/eq [add -1 -2] -3] } deftest "zero" { [assert/eq [add 0 0] 0] } }
### Test Results, not just success
defsuite "json/parse" { deftest "valid" { set r [json/parse "{\"a\":1}"] [assert/eq $r~a 1] } deftest "invalid" { set r [json/parse "not json"] [assert/truthy [err? $r]] } }
### Test that code throws
Wrap the call in a `try` and assert the catch branch ran:
defsuite "divide" { deftest "by zero throws" { set caught false try { [divide 1 0] } catch _e { set caught true } [assert/truthy $caught] } }
### Higher-order function tests
defsuite "map" { deftest "applies f" { set result @{ 1 2 3 } |> map { $it * 2 } |> collect [assert/eq $result @{ 2 4 6 }] } deftest "lazy on infinite" { set result [range 0 1000] |> filter even? |> take 3 |> collect [assert/eq $result @{ 0 2 4 }] } }
### Pipeline tests
defsuite "pipeline" { deftest "chain" { set result @{ 1 2 3 4 } |> map { $it * $it } |> filter { $it > 4 } |> collect [assert/eq $result @{ 9 16 }] } }
### User-defined type tests
deftype Point %{ :x %{:type :number} :y %{:type :number} } defsuite "Point" { deftest "construction" { set p %Point{ :x 3 :y 4 } [assert/eq $p~x 3] [assert/eq $p~y 4] } }
### Function-value tests
Functions are first-class; assert their return values:
defsuite "factorial" { def factorial {|n| if ($n <= 1) { 1 } { ($n * [factorial ($n - 1)]) } } deftest "base case" { [assert/eq [factorial 0] 1] } deftest "recursive" { [assert/eq [factorial 5] 120] } }
## Benchmarks
`defbench` declares a benchmark case. Run with `deft test --bench`:
defsuite "fib perf" { defbench "fib 20" { [fib 20] } }
The runner reports wall-clock time per iteration. Use it to spot
regressions in hot paths.
## Best Practices
1. **One concept per suite.** Group tests by the function or type
they exercise, not by output shape.
2. **Test names should read like specs.** `"add positive numbers"`,
not `"test1"`.
3. **Don't share mutable state across tests.** Each `deftest` should
construct its own fixtures.
4. **Prefer pure assertions over `echo` inspection.** Asserts are
machine-checkable; printed output isn't.
5. **Test failure modes, not just happy paths.** Wrap calls in `try`
to verify error behaviour.
## CI Integration
A typical GitHub Actions step:
```yaml
- name: Run tests
run: |
zig build deft -Doptimize=ReleaseFast
./zig-out/bin/deft test
```
For FFI tests:
```yaml
- name: Build with FFI
run: zig build deft -Dffi=true -Doptimize=ReleaseFast
- name: Run FFI tests
run: ./zig-out/bin/deft test test/test_ffi.dft
```
## Quick Reference
| Form | Purpose |
|---|---|
| `defsuite "name" { ... }` | Group tests |
| `deftest "name" { ... }` | Individual case |
| `defbench "name" { ... }` | Benchmark case |
| `[assert/eq actual expected]` | Assertion |
| `deft test [files...]` | Run tests |
| `deft test --bench` | Run benchmarks |