Zig includes a comprehensive testing framework built into the standard library. Tests are first-class citizens in Zig - they’re part of the language itself, not an external tool.
The testing module provides a special allocator that detects memory leaks:
lib/std/testing.zig:10-29
pub const FailingAllocator = @import("testing/FailingAllocator.zig");pub const failing_allocator = failing_allocator_instance.allocator();/// This should only be used in temporary test programs.pub const allocator = allocator_instance.allocator();pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = if (std.debug.sys_can_stack_trace) 10 else 0, .resize_stack_traces = true, // A unique value so that when a default-constructed // GeneralPurposeAllocator is incorrectly passed to testing allocator, or // vice versa, panic occurs. .canary = @truncate(0x2731e675c3a701ba),}) = b: { if (!builtin.is_test) @compileError("testing allocator used when not testing"); break :b .init;};
Usage:
const std = @import("std");const testing = std.testing;test "allocator detects leaks" { const allocator = testing.allocator; const memory = try allocator.alloc(u8, 100); defer allocator.free(memory); // Must free or test fails // Use memory...}
Always use testing.allocator in tests. It will automatically detect leaks and fail the test if memory isn’t freed properly.
/// This function is intended to be used only in tests. When the two values are not/// equal, prints diagnostics to stderr to show exactly how they are not equal,/// then returns a test failure error./// `actual` and `expected` are coerced to a common type using peer type resolution.pub inline fn expectEqual(expected: anytype, actual: anytype) !void { const T = @TypeOf(expected, actual); return expectEqualInner(T, expected, actual);}
/// This function is intended to be used only in tests. When the two slices are not/// equal, prints diagnostics to stderr to show exactly how they are not equal (with/// the differences highlighted in red), then returns a test failure error./// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void { const diff_index: usize = diff_index: { const shortest = @min(expected.len, actual.len); var index: usize = 0; while (index < shortest) : (index += 1) { if (!std.meta.eql(actual[index], expected[index])) break :diff_index index; } break :diff_index if (expected.len == actual.len) return else shortest; }; // Print detailed diagnostics...}
Verify that an expression returns a specific error:
lib/std/testing.zig:55-70
/// This function is intended to be used only in tests. It prints diagnostics to stderr/// and then returns a test failure error when actual_error_union is not expected_error.pub fn expectError(expected_error: anyerror, actual_error_union: anytype) !void { if (actual_error_union) |actual_payload| { print("expected error.{s}, found {any}\n", .{ @errorName(expected_error), actual_payload }); return error.TestExpectedError; } else |actual_error| { if (expected_error != actual_error) { print("expected error.{s}, found error.{s}\n", .{ @errorName(expected_error), @errorName(actual_error), }); return error.TestUnexpectedError; } }}
/// This function is intended to be used only in tests. When the actual value is/// not approximately equal to the expected value, prints diagnostics to stderr/// to show exactly how they are not equal, then returns a test failure error./// See `math.approxEqAbs` for more information on the tolerance parameter.pub inline fn expectApproxEqAbs(expected: anytype, actual: anytype, tolerance: anytype) !void { const T = @TypeOf(expected, actual, tolerance); return expectApproxEqAbsInner(T, expected, actual, tolerance);}/// See `math.approxEqRel` for more information on the tolerance parameter.pub inline fn expectApproxEqRel(expected: anytype, actual: anytype, tolerance: anytype) !void { const T = @TypeOf(expected, actual, tolerance); return expectApproxEqRelInner(T, expected, actual, tolerance);}
test "floating point comparison" { const a: f32 = 0.1 + 0.2; const b: f32 = 0.3; // Absolute tolerance try testing.expectApproxEqAbs(b, a, 0.0001); // Relative tolerance try testing.expectApproxEqRel(b, a, 0.01);}
/// This function is intended to be used only in tests. When the formatted result of the template/// and its arguments does not equal the expected text, it prints diagnostics to stderr to show how/// they are not equal, then returns an error.pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { if (@inComptime()) { var buffer: [std.fmt.count(template, args)]u8 = undefined; return expectEqualStrings(expected, try std.fmt.bufPrint(&buffer, template, args)); } const actual = try std.fmt.allocPrint(allocator, template, args); defer allocator.free(actual); return expectEqualStrings(expected, actual);}
# Run all testszig test myfile.zig# Run tests matching a patternzig test myfile.zig --test-filter "math"# Run a specific testzig test myfile.zig --test-filter "math.add"
/// Provides deterministic randomness in unit tests./// Initialized on startup. Read-only after that.pub var random_seed: u32 = 0;
const std = @import("std");const testing = std.testing;test "deterministic random" { var prng = std.Random.DefaultPrng.init(testing.random_seed); const random = prng.random(); const value = random.int(u32); // Same seed produces same value across test runs}