Cmdtest - CLI testing for Zig

Hey guys, I have published a new package called cmdtest.

cmdtest is my personal approach to write integration tests for my Zig CLI apps and I thought I’d share it for everyone to use.

Here’s how to use it:

  1. Fetch the latest release:

    zig fetch --save=cmdtest https://github.com/pyk/cmdtest/archive/v0.2.0.tar.gz
    

    This updates build.zig.zon.

  2. Write your test file. Example: test/mycli.zig.

     const std = @import("std");
     const cmdtest = @import("cmdtest");
     const testing = std.testing;
    
     test "via exe name" {
         const argv = &[_][]const u8{"mycli"};
         var result = try cmdtest.run(.{ .argv = argv });
         defer result.deinit();
    
         try testing.expectEqualStrings("project-exe\n", result.stderr);
     }
    
     test "via path" {
         const argv = &[_][]const u8{"./zig-out/bin/mycli"};
         var result = try cmdtest.run(.{ .argv = argv });
         defer result.deinit();
    
         try testing.expectEqualStrings("project-exe\n", result.stderr);
     }
    
  3. Register the test in build.zig:

     const std = @import("std");
     const cmdtest = @import("cmdtest");
    
     pub fn build(b: *std.Build) void {
         const target = b.standardTargetOptions(.{});
    
         // Your CLI
         const cli = b.addExecutable(.{
             .name = "mycli",
             .root_module = b.createModule(.{
                 .root_source_file = b.path("src/main.zig"),
                 .target = target,
             }),
         });
         b.installArtifact(cli);
    
         // Register new test
         const cli_test = cmdtest.add(b, .{
             .name = "mycli",
             .test_file = b.path("test/mycli.zig"),
         });
    
         const test_step = b.step("test", "Run tests");
         test_step.dependOn(&cli_test.step);
     }
    
  4. Run the tests:

    zig build test --summary all
    

I also wrote a blog post, like a “behind the scene” look at the things that I have learned while publishing this package. It covers Zig I/O, comptime vs runtime, and build.zig imports. If you’re interested, you can read it here

3 Likes

You might be interested in this thread:

2 Likes

I’ve pushed some changes to main branch. This changes allows you to test interactive CLI like this:

test "spawn: interactive mode" {
    const argv = &[_][]const u8{ "cmdtest", "--interactive" };
    var proc = try cmdtest.spawn(.{ .argv = argv });
    defer proc.deinit();

    try proc.writeToStdin("PING\n");
    try testing.expectEqualStrings("PONG", try proc.readLineFromStdout());

    try proc.writeToStdin("ECHO works\n");
    try testing.expectEqualStrings("works", try proc.readLineFromStdout());

    try proc.writeToStdin("EXIT\n");

    const term = try proc.child.wait();
    try testing.expectEqual(@as(u8, 0), term.Exited);
}