| name | ocaml-testing |
| description | Testing strategies for OCaml libraries. Use when discussing tests, alcotest, eio mocks, test structure, test-driven development, or cram tests in OCaml projects. |
| license | ISC |
OCaml Testing
Core Philosophy
- Unit Tests First: Prioritize unit tests for individual modules.
- 1:1 Test Coverage: Every
lib/*.ml should have test/test_*.ml.
- Isolated Tests: Each test independent, no external state.
- Clear Names: Describe what is tested, not how.
Test Organization
project/
├── lib/
│ ├── user.ml
│ └── auth.ml
├── test/
│ ├── dune
│ ├── test.ml # Main runner
│ ├── test_user.ml # Tests for user.ml
│ └── test_auth.ml # Tests for auth.ml
└── third_party/ # Fetched sources for reference
Rules:
- Test file
test_foo.ml tests library module foo.ml
- Every test module exports
suite : string * unit Alcotest.test_case list
- Main
test.ml aggregates all suites
Basic Test Structure
(* test/test_user.ml *)
let test_create () =
let user = User.create ~name:"Alice" in
Alcotest.(check string) "name" "Alice" (User.name user)
let test_validate_empty () =
let result = User.create ~name:"" in
Alcotest.(check bool) "fails" true (Result.is_error result)
let suite = ("user", [
"create", `Quick, test_create;
"validate_empty", `Quick, test_validate_empty;
])
(* test/test.ml *)
let () = Alcotest.run "MyProject" [
Test_user.suite;
Test_auth.suite;
]
Test Naming
- Suite names: lowercase, single word (
"user", "auth")
- Case names: lowercase with underscores (
"create", "parse_error")
Dune Configuration
(test
(name test)
(libraries mylib alcotest))
For Eio-based libraries:
(test
(name test)
(libraries mylib alcotest eio_main eio.mock))
Testing with Eio Mocks
Prefer mocks for deterministic, fast tests.
let test_with_mock_clock () =
Eio_mock.Backend.run @@ fun () ->
let clock = Eio_mock.Clock.make () in
Eio_mock.Clock.advance clock 1.0;
Alcotest.(check bool) "advanced" true true
let test_with_mock_flow () =
Eio_mock.Backend.run @@ fun () ->
let flow = Eio_mock.Flow.make "test" in
Eio_mock.Flow.on_read flow [
`Return "data";
`Raise End_of_file;
];
(* test with flow *)
Mock modules: Eio_mock.Backend, Eio_mock.Clock, Eio_mock.Flow, Eio_mock.Net, Eio_mock.Fs
Cram Tests (End-to-End)
For CLI/executable testing. Use directories ending in .t/.
test/
└── my_feature.t/
├── run.t # Test script
└── input.txt # Real test files (not cat << EOF)
Rules:
- Create actual files in directory, don't embed with
cat > file << EOF
- Test the compiled executable behaviour
- Use for integration/CLI tests, not unit tests
Coverage Checklist
For each module:
Running Tests
dune runtest
dune runtest --verbose
dune exec test/test.exe
dune test --instrument-with bisect_ppx
Best Practices
- Prefer mocks over real I/O - Fast, deterministic
- Test edge cases - Empty, max, invalid
- One assertion per test when practical
- Clean up resources - Even in tests
- Keep integration tests minimal - Most should be unit tests