⚡ Zig Guide LiveUnofficial
✓ Zig 0.17.0-dev.1454+5faa79730On an older Zig?

Testing

const std = @import("std");

test "expectEqual prints both values on failure" {
    // expect(a == b) only tells you "false". expectEqual tells you
    // expected 42, found 41. The expected value comes first.
    try std.testing.expectEqual(42, 41 + 1);
    try std.testing.expectEqual(@as(?u8, null), null);
}

test "slice and string comparisons show where they diverge" {
    try std.testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, &.{ 1, 2, 3 });

    // On failure this prints both strings and the first differing index.
    try std.testing.expectEqualStrings("hello", "hel" ++ "lo");

    try std.testing.expectStringStartsWith("zig build verify", "zig build");
}

fn parseDigit(c: u8) !u4 {
    if (c < '0' or c > '9') return error.NotADigit;
    return @intCast(c - '0');
}

test "expectError asserts the failure path" {
    // Untested error paths rot. This makes them first-class assertions.
    try std.testing.expectError(error.NotADigit, parseDigit('x'));
    try std.testing.expectEqual(7, try parseDigit('7'));
}

test "floats compare within a tolerance" {
    const third: f64 = 1.0 / 3.0;
    // Never == on computed floats; state how close is close enough.
    try std.testing.expectApproxEqAbs(0.333, third, 0.001);
    try std.testing.expectApproxEqRel(1.0, third * 3.0, std.math.floatEps(f64));
}

const Config = struct {
    name: []const u8,
    retries: u8,
};

test "expectEqualDeep follows pointers and slices" {
    const a = Config{ .name = "prod", .retries = 3 };
    const b = Config{ .name = "prod", .retries = 3 };

    // expectEqual on these would compare the slice pointers.
    // expectEqualDeep compares what they point at.
    try std.testing.expectEqualDeep(a, b);
}

test "expectFmt checks formatted output" {
    try std.testing.expectFmt("0x00ff", "0x{x:0>4}", .{255});
}

test "the test allocator reports leaks" {
    // std.testing.allocator fails the test if anything is still
    // allocated when the test returns. Forget this free and the test
    // fails with a stack trace of the leaked allocation.
    const gpa = std.testing.allocator;
    const buf = try gpa.alloc(u8, 64);
    defer gpa.free(buf);

    // It also detects double-free and use-after-free in test builds.
    try std.testing.expect(buf.len == 64);
}

expect tells you nothing; the others tell you what

try std.testing.expect(a == b) fails with “expected true, found false” and leaves you to add print statements. The typed assertions carry the values into the failure message:

AssertionReports on failure
expectEqual(expected, actual)both values
expectEqualStringsboth strings and the first differing byte
expectEqualSlices(T, ...)both slices and the diverging index
expectError(err, expr)which error, or that none was returned

The expected value comes first by convention. expectEqual infers its type from that first argument, which is why @as(?u8, null) sometimes needs the cast: it tells the comparison what type null is.

Assert the error paths too

Untested failure paths are where bugs hide. expectError makes “this input is rejected” a first-class assertion, as checkable as the success case. A parser test should pin both the digit it accepts and the character it refuses.

Floats need a tolerance

Never compare computed floats with ==. expectApproxEqAbs takes an absolute tolerance for values near a known magnitude; expectApproxEqRel takes a relative one, and std.math.floatEps(f64) is the natural bound for “as close as the type allows.”

expectEqualDeep for structures

expectEqual on two structs holding slices compares the slice pointers, which is almost never what you meant. expectEqualDeep follows pointers and slices and compares the pointed-at content.

The allocator is a test

std.testing.allocator fails the test if anything it handed out is still live when the test returns, so a missing free is a caught bug rather than a slow leak in production. In test builds it also detects double-free and use-after-free. Wire every allocation to a defer free and let the allocator police it.