Writing and Running Unit Tests in Zig with the zig test Command
Learn how to write Zig unit tests with the built‑in `zig test` command, from setup to execution and debugging.
20 Sept 2025, 02:16 UTC

Desired outcome
You want to verify that a piece of Zig code behaves correctly by writing automated unit tests and running them with the built‑in zig test command.
Prerequisites
- Install Zig version 0.11.0 or newer. Verify with
zig versionin a terminal. - Ensure the
zigexecutable is on yourPATHso the shell can find it. - Basic familiarity with editing text files; no special permissions are required beyond read/write access to your project directory.
Procedure
1. Set up a minimal project layout
Create a directory for the example, e.g., mkdir -p zig-test-demo && cd zig-test-demo. Inside, make a src folder:
mkdir src
Place your implementation in src/main.zig. For illustration, add a simple function that adds two unsigned 32‑bit integers:
// src/main.zig
pub fn add(a: u32, b: u32) u32 {
return a + b;
}
2. Write a test file
Create a separate test source, src/test.zig. Import the code under test and define test blocks using the test keyword. Each block receives a descriptive string and a body where assertions are made.
// src/test.zig
const std = @import("std");
const main = @import("main");
test "add returns correct sum" {
// expect checks a boolean condition; it fails the test if false.
try std.testing.expectEqual(u32, 42, main.add(20, 22));
// You can also use plain expect for true/false values.
try std.testing.expect(main.add(0, 0) == 0);
}
test "add handles overflow by wrapping" {
// Zig unsigned integers wrap on overflow; verify the behaviour.
try std.testing.expectEqual(u32, 0, main.add(@max(u32), 1));
}
If you prefer to keep tests alongside the code, you could place the test blocks directly in src/main.zig and run zig test src/main.zig instead.
3. Run the test suite
From the project root, execute:
zig test src/test.zig
The command does the following:
- Compiles a temporary test binary that links your production code and the test code.
- Runs that binary in the same process.
- Prints each test name and result.
No extra build system is required; the command works out‑of‑the‑box.
Expected checks
After the command finishes, verify two things:
- The process exit status is zero. In a POSIX shell you can check with
echo $?immediately after the run. - The last line of output is exactly
All tests passed.
If either condition fails, at least one test did not succeed.
Recovery options
Fixing a failing test
Edit either the production code or the test itself, then rerun zig test src/test.zig. Because the test binary is rebuilt each time, changes are picked up immediately.
Running a subset of tests
While debugging, limit execution to tests whose names contain a substring:
zig test src/test.zig --test-filter "overflow"
This compiles the same binary but only executes matching test blocks, speeding up iteration.
Inspecting the generated test binary
If you need to examine the binary (e.g., to run it under a memory checker), ask Zig to emit it to a known location:
zig test src/test.zig -femit-bin ./zig-out/bin/my_test
The produced file can be run directly or fed to tools like valgrind or addresssanitizer.
Limitations and practical verification
The default zig test build uses debug mode, which includes safety checks but no optimizations. For performance‑critical assertions you may prefer a release build via a custom build.zig file:
const std = @import("std"); pub fn build(b: *std.Build) void { const test = b.addTest(.{ .root_source_file = .{ .path = "src/test.zig" }, .optimize = .ReleaseFast, }); b.runArtifact(test); }Then run
zig build test. Remember that changing optimization can affect code that relies on undefined behaviour; verify that your tests still pass.To confirm the test harness is active, deliberately insert a failing test, such as
try std.testing.expect(false);, runzig test src/test.zig, and observe a non‑zero exit code and a failure message that includes the test name and source location.Summary
By following these steps you have a repeatable way to write, execute, and debug unit tests in Zig using the built‑in
zig testcommand. The workflow requires only a working Zig installation, a source file, and a test file; no external test framework is needed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.