Gleam Testing
Gleam tests are regular Gleam functions with names ending in _test. The gleeunit test runner discovers and executes them automatically. Good tests verify behavior, catch regressions, and document how your code works through examples.
Test Project Setup
// gleam.toml — gleeunit is a dev dependency
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
// test/my_app_test.gleam
import gleeunit
import gleeunit/should
pub fn main() {
gleeunit.main() // required: runs all tests
}
Writing Your First Test
import gleeunit/should
import my_app/math
pub fn add_test() {
math.add(2, 3)
|> should.equal(5)
}
pub fn subtract_test() {
math.subtract(10, 4)
|> should.equal(6)
}
Run tests with:
gleam test
Test Output
──────────────────────────────────────────────────
Compiling my_app
Finished in 0.35s
..
2 tests, 0 failures ← each dot = one passing test
The should Module
should Assertion Functions
──────────────────────────────────────────────────
should.equal(expected) → value == expected
should.not_equal(unexpected) → value != unexpected
should.be_true() → value == True
should.be_false() → value == False
should.be_ok() → value is Ok(_)
should.be_error() → value is Error(_)
should.be_some() → value is Some(_)
should.be_none() → value is None
import gleeunit/should
import gleam/int
pub fn parse_valid_test() {
int.parse("42")
|> should.be_ok()
|> should.equal(42)
}
pub fn parse_invalid_test() {
int.parse("not a number")
|> should.be_error()
}
Testing Custom Types
import my_app/order.{type Order, Order, Fulfilled, Cancelled}
import gleeunit/should
pub fn fulfil_order_test() {
let order = Order(id: 1, status: Pending, total: 500.0)
let fulfilled = order.fulfil(order)
fulfilled.status |> should.equal(Fulfilled)
}
pub fn cancel_fulfilled_order_test() {
let order = Order(id: 1, status: Fulfilled, total: 500.0)
let result = order.cancel(order)
result |> should.be_error()
}
Test File Organization
Mirror src/ structure in test/:
──────────────────────────────────────────────────
src/
math.gleam
user/
auth.gleam
profile.gleam
test/
math_test.gleam ← tests for math
user/
auth_test.gleam ← tests for auth
profile_test.gleam ← tests for profile
Testing with Setup Data
import gleeunit/should
import my_app/user
fn make_user() {
user.User(
id: 1,
name: "Test User",
email: "test@example.com",
role: user.Admin
)
}
pub fn user_is_admin_test() {
make_user()
|> user.is_admin
|> should.be_true()
}
pub fn user_display_name_test() {
make_user()
|> user.display_name
|> should.equal("Test User")
}
Testing Pipelines
Pipelines in tests read like documentation:
import my_app/text_processor
import gleeunit/should
pub fn normalize_test() {
" HELLO WORLD "
|> text_processor.normalize
|> should.equal("hello world")
}
pub fn word_count_test() {
"the quick brown fox"
|> text_processor.word_count
|> should.equal(4)
}
Testing Error Paths
import my_app/validator
import gleeunit/should
pub fn empty_name_test() {
validator.validate_name("")
|> should.be_error()
}
pub fn short_password_test() {
validator.validate_password("abc")
|> should.be_error()
}
pub fn valid_password_test() {
validator.validate_password("SecurePass123!")
|> should.be_ok()
}
Running Specific Tests
gleam test // run all tests
gleam test --module math_test // run one test module
Test Naming Conventions
Good Test Names — describe the scenario:
──────────────────────────────────────────────────
pub fn add_two_positive_numbers_test()
pub fn returns_error_for_empty_input_test()
pub fn discount_applied_when_user_is_premium_test()
pub fn list_first_on_empty_list_test()
Bad Test Names — too vague:
──────────────────────────────────────────────────
pub fn test1_test()
pub fn math_test() ← conflicts with module name
pub fn works_test()
Property-Based Testing
The qcheck package adds property-based testing — generating random inputs to find edge cases automatically:
// gleam add qcheck --dev
import qcheck
pub fn reverse_twice_is_identity_test() {
use list <- qcheck.run(qcheck.list_of(qcheck.int()))
let double_reversed = list |> list.reverse |> list.reverse
double_reversed == list
}
Practical Example — Full Module Test
// test/cart_test.gleam
import gleeunit
import gleeunit/should
import my_app/cart.{Cart, Item}
pub fn main() { gleeunit.main() }
fn empty_cart() { Cart(items: [], discount: 0.0) }
pub fn empty_cart_total_test() {
empty_cart()
|> cart.total
|> should.equal(0.0)
}
pub fn add_item_increases_total_test() {
empty_cart()
|> cart.add_item(Item(name: "Pen", price: 10.0, qty: 3))
|> cart.total
|> should.equal(30.0)
}
pub fn discount_applied_test() {
empty_cart()
|> cart.add_item(Item(name: "Book", price: 200.0, qty: 1))
|> cart.apply_discount(0.10)
|> cart.total
|> should.equal(180.0)
}
Key Points
Testing Essentials
──────────────────────────────────────────────────
1. Test files live in test/, named *_test.gleam
2. Test functions end in _test
3. Every test file needs: pub fn main() { gleeunit.main() }
4. Use |> should.equal(expected) for assertions
5. Use should.be_ok() / should.be_error() for Results
6. Use helper functions to build shared test data
7. gleam test runs all tests; --module targets one file
8. Name tests to describe the exact scenario tested
Testing in Gleam is fast, readable, and low-friction. Because the type system catches many bugs at compile time, tests focus on behavior and business logic rather than defending against type errors. Well-named tests become the most accurate documentation your codebase has.
