Skip to content

CogitatorTech/minish

Minish Logo

Minish

Tests License Examples Docs Zig Version Release

A property-based testing framework for Zig


Minish is a small property-based testing framework for Zig, inspired by QuickCheck and Hypothesis.

What is Property-based Testing?

Property-based testing is a way of testing software by defining properties that should always hold. Compared to typical example-based testing (like unit tests), instead of writing individual test cases with specific inputs and expected outputs, you define general properties about your code's behavior. The testing framework then generates a wide range of random inputs to verify that these properties hold for all cases.

Given a piece of code like a function and its property, a property-based testing workflow normally involves the following steps:

  1. Generating a lot of random inputs.
  2. Finding cases where the input causes the property to fail.
  3. Finding smaller subsets of the failing input that still cause the failure (this is called "shrinking").

For example, consider the property of a reverse function that states that reversing a string twice should return the original string. In property-based testing, you would define this property and let the framework generate a lot of random strings to test it. If it finds a string that makes the property fail (due to a bug in the reverse function, for example), it will then try to shrink that string to a simpler or shorter case that still makes the property fail.

Here is a brief comparison between example-based testing and property-based testing paradigms:

Criterion Example-based Testing Property-based Testing
Input Hand-written specific values Auto-generated random values
Coverage Only cases you can think of Discovers edge cases automatically
Debugging Exact failing inputs are known Shrinks to minimal failing case
Effort Write a lot of test cases Define one property, test with many inputs

Why Minish?

  • Written in pure Zig with no external dependencies
  • Includes over 25 built-in generators (like for integers, floats, strings, lists, structs, UUIDs, timestamps, etc.)
  • Combinators to build complex generators from simple ones (map, filter, flatMap, frequency)
  • Supports automatic shrinking for integers, floats, strings, lists, tuples, and arrays
  • Supports reproducible failures and verbose mode
  • Configurable and easy to integrate into existing Zig projects

See ROADMAP.md for the list of implemented and planned features.

Important

Minish is in early development, so bugs and breaking changes are expected. Please use the issues page to report bugs or request features.


Getting Started

You can add Minish to your project and start using it by following the steps below.

Installation

Run the following command in the root directory of your project to download Minish:

zig fetch --save=minish "https://github.com/CogitatorTech/minish/archive/<branch_or_tag>.tar.gz"

Replace <branch_or_tag> with the desired branch or release tag, like main (for the development version) or v0.1.0. This command will download Minish and add it to Zig's global cache and update your project's build.zig.zon file.

Note

Minsih is developed and tested with Zig version 0.15.2.

Adding to Build Script

Next, modify your build.zig file to make Minish available to your build target as a module.

pub fn build(b: *std.Build) void {
    // ... the existing setup ...

    const minish_dep = b.dependency("minish", .{});
    const minish_module = minish_dep.module("minish");
    exe.root_module.addImport("minish", minish_module);
}

A Simple Example

Finally, you can @import("minish") and start using it in your Zig project.

const std = @import("std");
const minish = @import("minish");
const gen = minish.gen;

// Helper function to reverse a string
fn reverse(allocator: std.mem.Allocator, s: []const u8) ![]u8 {
    const result = try allocator.alloc(u8, s.len);
    for (s, 0..) |c, i| {
        result[s.len - 1 - i] = c;
    }
    return result;
}

// Property: reversing a string twice returns the original
fn reverse_twice_is_identity(s: []const u8) !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const once = try reverse(allocator, s);
    defer allocator.free(once);

    const twice = try reverse(allocator, once);
    defer allocator.free(twice);

    try std.testing.expectEqualStrings(s, twice);
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Generate random strings and test the property
    const string_gen = gen.string(.{
        .min_len = 0,
        .max_len = 100,
        .charset = .alphanumeric,
    });

    try minish.check(allocator, string_gen, reverse_twice_is_identity, .{
        .num_runs = 100,
    });
}

Documentation

You can find the API documentation for the latest release of Minish here.

Alternatively, you can use the make docs command to generate the documentation for the current version of Minish. This will generate HTML documentation in the docs/api directory, which you can serve locally with make serve-docs and view in a web browser.

Examples

Check out the examples directory for example usages of Minish.


Contributing

See CONTRIBUTING.md for details on how to make a contribution.

License

Minish is licensed under the Apache License, Version 2.0 (see LICENSE).

Acknowledgements

  • The logo is from SVG Repo with some modifications.

Sponsor this project