# Running Tests

### Running Tests

Zig has built-in support for tests.

You can test one file directly with:

```bash
zig test src/main.zig
```

That command compiles the file, finds its `test` blocks, and runs them.

Inside a project, you usually run tests through `build.zig`:

```bash
zig build test
```

This gives the project one standard test command.

#### A Simple Test

A Zig test uses a `test` block:

```zig
const std = @import("std");

fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "add returns the sum" {
    try std.testing.expect(add(2, 3) == 5);
}
```

The test name is:

```zig
"add returns the sum"
```

The check is:

```zig
try std.testing.expect(add(2, 3) == 5);
```

If the expression is true, the test passes.

If it is false, the test fails.

#### Testing a File Directly

For a small file, use:

```bash
zig test src/main.zig
```

This is useful while learning because it avoids writing a build file.

But real projects usually need more:

```text
target options
optimization options
dependencies
test filters
custom test steps
integration tests
```

That is where `build.zig` becomes useful.

#### Adding Tests to `build.zig`

A common test setup looks like this:

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const unit_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    const run_unit_tests = b.addRunArtifact(unit_tests);

    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_unit_tests.step);
}
```

Now this command works:

```bash
zig build test
```

The build file creates a test artifact, creates a run step for it, and connects that run step to the named `test` step.

#### The Three Pieces

This line compiles the tests:

```zig
const unit_tests = b.addTest(.{
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    }),
});
```

This line creates a command that runs the test artifact:

```zig
const run_unit_tests = b.addRunArtifact(unit_tests);
```

This creates the public build command:

```zig
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
```

So the command:

```bash
zig build test
```

means:

```text
compile the tests
run the tests
finish the test step
```

#### Tests in Separate Files

Many projects keep tests close to the code.

For example:

```text
src/
  math.zig
  main.zig
```

`src/math.zig`:

```zig
const std = @import("std");

pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "add" {
    try std.testing.expect(add(2, 3) == 5);
}
```

`src/main.zig`:

```zig
const std = @import("std");
const math = @import("math.zig");

pub fn main() void {
    std.debug.print("{}\n", .{math.add(2, 3)});
}
```

If you test only `src/main.zig`, Zig will compile the imports needed by `main.zig`. But test discovery depends on the test root.

For libraries, it is common to have a root file that imports the modules you want tested.

Example `src/root.zig`:

```zig
pub const math = @import("math.zig");

test {
    _ = math;
}
```

Then `build.zig` can test `src/root.zig`.

```zig
const unit_tests = b.addTest(.{
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    }),
});
```

This gives the test runner a clear root for the package.

#### Testing Libraries

If your project builds a library, tests often target the library root:

```zig
const lib_tests = b.addTest(.{
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

const run_lib_tests = b.addRunArtifact(lib_tests);

const test_step = b.step("test", "Run library tests");
test_step.dependOn(&run_lib_tests.step);
```

This is better than testing an executable entry point when most logic lives in library modules.

A good project often keeps `main.zig` small and puts testable logic in separate modules.

#### Testing Executables

You can also test code used by an executable.

Suppose your executable uses `src/main.zig` as its root. Then:

```zig
const exe_tests = b.addTest(.{
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    }),
});
```

This is acceptable for small programs.

For larger programs, split the core logic into a library module and keep `main.zig` focused on startup, argument parsing, and wiring.

#### Test Dependencies

If your application uses external dependencies, tests may need them too.

Adding a dependency to the executable does not automatically add it to the test artifact.

For example:

```zig
const parser = b.dependency("parser", .{
    .target = target,
    .optimize = optimize,
});

exe.root_module.addImport("parser", parser.module("parser"));
```

If tests also import `parser`, add it to the test root module:

```zig
unit_tests.root_module.addImport("parser", parser.module("parser"));
```

Each root module has its own imports.

This is a common source of beginner errors.

#### Filtering Tests

When using `zig test` directly, you can often pass a test filter to run matching tests.

For example:

```bash
zig test src/root.zig --test-filter add
```

This runs tests whose names match the filter.

In a build file, you can expose a filter as a build option:

```zig
const test_filter = b.option(
    []const u8,
    "test-filter",
    "Skip tests that do not match filter",
);
```

Then pass it to the test artifact:

```zig
if (test_filter) |filter| {
    unit_tests.filter = filter;
}
```

Now you can run:

```bash
zig build test -Dtest-filter=add
```

This is useful when a project has many tests and you want to focus on one area.

#### Tests with Build Options

Tests can receive build options just like executables.

```zig
const options = b.addOptions();
options.addOption(bool, "enable_logging", true);

unit_tests.root_module.addOptions("build_options", options);
```

Then test code can import:

```zig
const build_options = @import("build_options");
```

This lets tests run under the same configuration as the program.

For example, you might test with:

```bash
zig build test -Denable-logging=true
```

or:

```bash
zig build test -Doptimize=ReleaseSafe
```

#### Testing in Different Modes

Tests usually run in `Debug` mode by default.

You can test release behavior too:

```bash
zig build test -Doptimize=ReleaseSafe
zig build test -Doptimize=ReleaseFast
zig build test -Doptimize=ReleaseSmall
```

This can reveal bugs that only appear under optimization.

A practical workflow is:

```bash
zig build test
zig build test -Doptimize=ReleaseSafe
zig build -Doptimize=ReleaseFast
```

Use `Debug` while developing. Use `ReleaseSafe` before shipping. Use `ReleaseFast` for final performance builds when appropriate.

#### Cross-Target Tests

Tests can be compiled for another target:

```bash
zig build test -Dtarget=x86_64-linux
```

But running them is different.

If you are not on Linux, your machine may not be able to execute the Linux test binary. Cross compilation can produce the test artifact, but execution needs a compatible runtime, emulator, device, or operating system.

So keep this distinction clear:

```text
compile tests for a target
run tests on a target
```

They are separate operations.

#### Integration Tests

Unit tests check small pieces of code.

Integration tests check larger behavior: running a program, reading files, talking to a local server, parsing real input, or checking command-line output.

A simple integration test can be another Zig file:

```text
tests/
  cli_test.zig
```

In `build.zig`:

```zig
const integration_tests = b.addTest(.{
    .root_module = b.createModule(.{
        .root_source_file = b.path("tests/cli_test.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

const run_integration_tests = b.addRunArtifact(integration_tests);

test_step.dependOn(&run_integration_tests.step);
```

Now:

```bash
zig build test
```

runs both unit tests and integration tests, because both run steps are dependencies of the same `test` step.

#### A Complete Test Setup

Here is a compact project-style setup:

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    b.installArtifact(exe);

    const unit_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/root.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    const run_unit_tests = b.addRunArtifact(unit_tests);

    const integration_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("tests/integration.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    const run_integration_tests = b.addRunArtifact(integration_tests);

    const test_step = b.step("test", "Run all tests");
    test_step.dependOn(&run_unit_tests.step);
    test_step.dependOn(&run_integration_tests.step);
}
```

This gives one command:

```bash
zig build test
```

and that command runs all tests connected to the test step.

#### Common Mistakes

A common mistake is creating a test artifact but never running it.

This compiles tests:

```zig
const unit_tests = b.addTest(.{ ... });
```

But you still need:

```zig
const run_unit_tests = b.addRunArtifact(unit_tests);
```

and:

```zig
test_step.dependOn(&run_unit_tests.step);
```

Another mistake is forgetting imports on the test root module.

If the executable has:

```zig
exe.root_module.addImport("parser", parser.module("parser"));
```

the test artifact may also need:

```zig
unit_tests.root_module.addImport("parser", parser.module("parser"));
```

Another mistake is testing only `main.zig` when the real logic lives elsewhere. Prefer testing the library root or specific modules.

#### The Important Idea

Testing in Zig has two levels.

For one file, use:

```bash
zig test file.zig
```

For a project, define a test step in `build.zig`:

```zig
const unit_tests = b.addTest(.{ ... });
const run_unit_tests = b.addRunArtifact(unit_tests);

const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_unit_tests.step);
```

Then use:

```bash
zig build test
```

That gives your project a stable, repeatable test command.

