{"id":16023137,"url":"https://github.com/followtheprocess/test","last_synced_at":"2026-04-13T08:01:34.478Z","repository":{"id":172544290,"uuid":"649365914","full_name":"FollowTheProcess/test","owner":"FollowTheProcess","description":"A lightweight test helper package 🧪","archived":false,"fork":false,"pushed_at":"2024-12-09T11:19:42.000Z","size":124,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-09T12:25:14.984Z","etag":null,"topics":["go","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FollowTheProcess.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-04T16:13:21.000Z","updated_at":"2024-12-09T11:19:45.000Z","dependencies_parsed_at":"2023-10-11T14:45:09.068Z","dependency_job_id":"993f038e-9fa0-4e41-978e-a7cf9497db84","html_url":"https://github.com/FollowTheProcess/test","commit_stats":{"total_commits":66,"total_committers":3,"mean_commits":22.0,"dds":"0.28787878787878785","last_synced_commit":"597eb1ea1f568ebc5e8659e62a7b658c619c3872"},"previous_names":["followtheprocess/test"],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FollowTheProcess%2Ftest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FollowTheProcess%2Ftest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FollowTheProcess%2Ftest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FollowTheProcess%2Ftest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FollowTheProcess","download_url":"https://codeload.github.com/FollowTheProcess/test/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228930962,"owners_count":17993630,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","testing"],"created_at":"2024-10-08T19:00:51.047Z","updated_at":"2026-04-13T08:01:34.471Z","avatar_url":"https://github.com/FollowTheProcess.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# test\n\n[![License](https://img.shields.io/github/license/FollowTheProcess/test)](https://github.com/FollowTheProcess/test)\n[![Go Reference](https://pkg.go.dev/badge/go.followtheprocess.codes/test.svg)](https://pkg.go.dev/go.followtheprocess.codes/test)\n[![Go Report Card](https://goreportcard.com/badge/github.com/FollowTheProcess/test)](https://goreportcard.com/report/github.com/FollowTheProcess/test)\n[![GitHub](https://img.shields.io/github/v/release/FollowTheProcess/test?logo=github\u0026sort=semver)](https://github.com/FollowTheProcess/test)\n[![CI](https://github.com/FollowTheProcess/test/workflows/CI/badge.svg)](https://github.com/FollowTheProcess/test/actions?query=workflow%3ACI)\n[![codecov](https://codecov.io/gh/FollowTheProcess/test/branch/main/graph/badge.svg)](https://codecov.io/gh/FollowTheProcess/test)\n\n***A lightweight test helper package*** 🧪\n\n## Project Description\n\n`test` is my take on a handy, lightweight Go test helper package. Inspired by [matryer/is], [earthboundkid/be] and others.\n\nIt provides a lightweight, but useful, extension to the std lib testing package with a friendlier and hopefully intuitive API. You definitely don't need it,\nbut might find it useful anyway 🙂\n\n## Installation\n\n```shell\ngo get go.followtheprocess.codes/test@latest\n```\n\n## Usage\n\n`test` is as easy as...\n\n```go\nfunc TestSomething(t *testing.T) {\n    test.Equal(t, \"hello\", \"hello\") // Obviously fine\n    test.Equal(t, \"hello\", \"there\") // Fails\n\n    test.NotEqual(t, 42, 27) // Passes, these are not equal\n    test.NotEqual(t, 42, 42) // Fails\n\n    test.NearlyEqual(t, 3.0000000001, 3.0) // Look, floats handled easily!\n\n    err := doSomething()\n    test.Ok(t, err) // Fails if err != nil\n    test.Err(t, err) // Fails if err == nil\n\n    test.True(t, true) // Passes\n    test.False(t, true) // Fails\n}\n```\n\n### Add Additional Context\n\n`test` provides a number of options to decorate your test log with useful context:\n\n```go\nfunc TestDetail(t *testing.T) {\n    test.Equal(t, \"apples\", \"oranges\", test.Title(\"Fruit scramble!\"), test.Context(\"Apples are not oranges!\"))\n}\n```\n\nWill get you an error log in the test that looks like this...\n\n```plaintext\n--- FAIL: TestDemo (0.00s)\n    test_test.go:501:\n        Fruit scramble!\n        ---------------\n\n        Got:    apples\n        Wanted: oranges\n\n        (Apples are not oranges!)\n\nFAIL\n```\n\n### Non Comparable Types\n\n`test` uses generics under the hood for most of the comparison, which is great, but what if your types don't satisfy `comparable`. We also provide\n`test.EqualFunc` and `test.NotEqualFunc` for those exact situations!\n\nThese allow you to pass in a custom comparator function for your type, if your comparator function returns true, the types are considered equal.\n\n```go\nfunc TestNonComparableTypes(t *testing.T) {\n    // Slices do not satisfy comparable\n    a := []string{\"hello\", \"there\"}\n    b := []string{\"hello\", \"there\"}\n    c := []string{\"general\", \"kenobi\"}\n\n    // Custom function, returns true if things should be considered equal\n    sliceEqual := func(a, b, []string) { return true } // Cheating\n\n    test.EqualFunc(t, a, b, sliceEqual) // Passes\n\n    // Can also use any function here\n    test.EqualFunc(t, a, b, slices.Equal) // Also passes :)\n\n    test.EqualFunc(t, a, c, slices.Equal) // Fails\n}\n```\n\nYou can also use this same pattern for custom user defined types, structs etc.\n\n### Table Driven Tests\n\nTable driven tests are great! But when you test errors too it can get a bit awkward, you have to do the `if (err != nil) != tt.wantErr` thing and I personally\n*always* have to do the boolean logic in my head to make sure I got that right. Enter `test.WantErr`:\n\n```go\nfunc TestTableThings(t *testing.T) {\n    tests := []struct {\n        name    string\n        want    int\n        wantErr bool\n    }{\n        {\n            name:    \"no error\",\n            want:    4,\n            wantErr: false,\n        },\n        {\n            name:    \"yes error\",\n            want:    4,\n            wantErr: true,\n        },\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            got, err := SomeFunction()\n\n            test.WantErr(t, err, tt.wantErr)\n            test.Equal(t, got, tt.want)\n        })\n    }\n}\n```\n\nWhich is basically semantically equivalent to:\n\n```go\nfunc TestTableThings(t *testing.T) {\n    tests := []struct {\n        name    string\n        want    int\n        wantErr bool\n    }{\n        {\n            name:    \"no error\",\n            want:    4,\n            wantErr: false,\n        },\n        {\n            name:    \"yes error\",\n            want:    4,\n            wantErr: true,\n        },\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            got, err := SomeFunction()\n\n            if tt.wantErr {\n                test.Err(t, err)\n            } else {\n                test.Ok(t, err)\n            }\n            test.Equal(t, got, tt.want)\n        })\n    }\n}\n```\n\n### Capturing Stdout and Stderr\n\nWe've all been there, trying to test a function that prints but doesn't accept an `io.Writer` as a destination 🙄.\n\nThat's where `test.CaptureOutput` comes in!\n\n```go\nfunc TestOutput(t *testing.T) {\n    // Function that prints to stdout and stderr, but imagine this is defined somewhere else\n    // maybe a 3rd party library that you don't control, it just prints and you can't tell it where\n    fn := func() error {\n        fmt.Fprintln(os.Stdout, \"hello stdout\")\n        fmt.Fprintln(os.Stderr, \"hello stderr\")\n\n        return nil\n    }\n\n    // CaptureOutput to the rescue!\n    stdout, stderr := test.CaptureOutput(t, fn)\n\n    test.Equal(t, stdout, \"hello stdout\\n\")\n    test.Equal(t, stderr, \"hello stderr\\n\")\n}\n```\n\nUnder the hood `CaptureOutput` temporarily captures both streams, copies the data to a buffer and returns the output back to you, before cleaning everything back up again.\n\n### See Also\n\n- [FollowTheProcess/snapshot] for golden file/snapshot testing 📸\n\n### Credits\n\nThis package was created with [copier] and the [FollowTheProcess/go_copier] project template.\n\n[copier]: https://copier.readthedocs.io/en/stable/\n[FollowTheProcess/go_copier]: https://github.com/FollowTheProcess/go_copier\n[matryer/is]: https://github.com/matryer/is\n[earthboundkid/be]: https://github.com/earthboundkid/be\n[FollowTheProcess/snapshot]: https://github.com/FollowTheProcess/snapshot\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffollowtheprocess%2Ftest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffollowtheprocess%2Ftest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffollowtheprocess%2Ftest/lists"}