{"id":13564652,"url":"https://github.com/shoenig/test","last_synced_at":"2025-05-15T17:03:16.080Z","repository":{"id":38191315,"uuid":"484903945","full_name":"shoenig/test","owner":"shoenig","description":"A modern generic testing assertions library for Go","archived":false,"fork":false,"pushed_at":"2025-03-01T16:17:29.000Z","size":316,"stargazers_count":181,"open_issues_count":4,"forks_count":10,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-07T22:07:59.583Z","etag":null,"topics":["assertions","assertions-library","generic","go","golang","test","testing","testing-tools"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shoenig.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-24T02:23:01.000Z","updated_at":"2025-03-30T10:52:19.000Z","dependencies_parsed_at":"2023-02-15T18:01:39.298Z","dependency_job_id":"c47b6354-b800-483d-b9de-a023085e9d85","html_url":"https://github.com/shoenig/test","commit_stats":{"total_commits":145,"total_committers":7,"mean_commits":"20.714285714285715","dds":0.3931034482758621,"last_synced_commit":"e86c733077730e338984b9ac37166cf80441dab8"},"previous_names":[],"tags_count":46,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shoenig%2Ftest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shoenig%2Ftest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shoenig%2Ftest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shoenig%2Ftest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shoenig","download_url":"https://codeload.github.com/shoenig/test/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254384937,"owners_count":22062421,"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":["assertions","assertions-library","generic","go","golang","test","testing","testing-tools"],"created_at":"2024-08-01T13:01:34.077Z","updated_at":"2025-05-15T17:03:16.062Z","avatar_url":"https://github.com/shoenig.png","language":"Go","readme":"# test\n\n\u003cimg align=\"right\" width=\"240\" height=\"244\" src=\"https://i.imgur.com/gmn5mIo.png\"\u003e\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/shoenig/test.svg)](https://pkg.go.dev/github.com/shoenig/test)\n[![MPL License](https://img.shields.io/github/license/shoenig/test?color=g\u0026style=flat-square)](https://github.com/shoenig/test/blob/main/LICENSE)\n[![Run CI Tests](https://github.com/shoenig/test/actions/workflows/ci.yaml/badge.svg)](https://github.com/shoenig/test/actions/workflows/ci.yaml)\n\n`test` is a modern and generics oriented testing assertions library for Go.\n\nThere are five key packages,\n\n- `must` - assertions causing test failure and halt the test case immediately\n- `test` - assertions causing test failure and allow the test case to continue\n- `wait` - utilities for waiting on conditionals in tests\n- `skip` - utilities for skipping test cases in some situations\n- `util` - utilities for writing concise tests, e.g. managing temp files\n- `portal` - utilities for allocating free ports for network listeners in tests\n\n### Changes\n:ballot_box_with_check: v1.11.0 adds an ErrorAs helper\n\n - FS examples are more reliable\n - Examples run on non-Unix OS when possible\n\n:ballot_box_with_check: v1.10.0 adds a `util` package for helpers that return values\n\n - Adds ability to create and automatically clean up temporary files\n - Adds `SliceEqOp` and `MapEqOp` helpers\n\n:ballot_box_with_check: v1.9.0 substantially improves filesystem tests\n\n - Greater compatibility with Windows\n - Fixed assertions on possible errors\n\n:ballot_box_with_check: v1.8.0 introduces the `skip` package for skipping tests!\n\n - New helper functions for skipping out tests based on some given criteria\n\n:ballot_box_with_check: v1.7.0 marks the first stable release!\n\n - Going forward no breaking changes will be made without a v2 major version\n\n:ballot_box_with_check: v0.6.0 adds support for custom `cmp.Option` values\n\n - Adds ability to customize `cmp.Equal` behavior via `cmp.Option` arguments\n - Adds assertions for existence of single map key\n - Fixes some error outputs\n\n### Requirements\n\nOnly depends on `github.com/google/go-cmp`.\n\nThe minimum Go version is `go1.18`.\n\n### Install\n\nUse `go get` to grab the latest version of `test`.\n\n```shell\ngo get -u github.com/shoenig/test@latest\n```\n\n### Influence\n\nThis library was made after a ~decade of using [testify](https://github.com/stretchr/testify),\nquite possibly the most used library in the whole Go ecosystem. All credit of\ninspiration belongs them.\n\n### Philosophy\n\nGo has always lacked a strong definition of equivalency, and until recently lacked the\nlanguage features necessary to make type-safe yet generic assertive statements based on\nthe contents of values.\n\nThis `test` (and companion `must`) package aims to provide a test-case assertion library\nwhere the caller is in control of how types are compared, and to do so in a strongly typed\nway - avoiding erroneous comparisons in the first place.\n\nGenerally there are 4 ways of asserting equivalence between types.\n\n#### the `==` operator\n\nFunctions like `EqOp` and `ContainsOp` work on types that are `comparable`, i.e., are\ncompatible with Go's built-in `==` and `!=` operators.\n\n#### a comparator function\n\nFunctions like `EqFunc` and `ContainsFunc` work on any type, as the caller passes in a\nfunction that takes two arguments of that type, returning a boolean indicating equivalence.\n\n#### an `.Equal` method\n\nFunctions like `Equal` and `ContainsEqual` work on types implementing the `EqualFunc`\ngeneric interface (i.e. implement an `.Equal` method). The `.Equal` method is called\nto determine equivalence.\n\n#### the `cmp.Equal` or `reflect.DeepEqual` functions\n\nFunctions like `Eq` and `Contains` work on any type, using the `cmp.Equal` or `reflect.DeepEqual`\nfunctions to determine equivalence. Although this is the easiest / most compatible way\nto \"just compare stuff\", it's the least deterministic way of comparing instances of a type.\nChanges to the underlying types may cause unexpected changes in their equivalence (e.g.,\nthe addition of unexported fields, function field types, etc.). Assertions that make\nuse of `cmp.Equal` configured with custom `cmp.Option` values.\n\n#### output\n\nWhen possible, a nice `diff` output is created to show why an equivalence has failed. This\nis done via the `cmp.Diff` function. For incompatible types, their `GoString` values are\nprinted instead.\n\nAll output is directed through `t.Log` functions, and is visible only if test verbosity is\nturned on (e.g., `go test -v`).\n\n#### fail fast vs. fail later\n\nThe `test` and `must` packages are identical, except for how test cases behave when encountering\na failure. Sometimes it is helpful for a test case to continue running even though a failure has\noccurred (e.g., it contains cleanup logic not captured via a `t.Cleanup` function). Other times, it\nmakes sense to fail immediately and stop the test case execution.\n\n### `go-cmp` Options\n\nThe test assertions that rely on `cmp.Equal` can be customized in how objects\nare compared by [specifying custom](https://github.com/google/go-cmp/blob/master/cmp/options.go#L16)\n`cmp.Option` values. These can be configured through `test.Cmp` and `must.Cmp` helpers.\nGoogle provides some common custom behaviors in the [cmpopts](https://github.com/google/go-cmp/tree/master/cmp/cmpopts)\npackage. The [protocmp](https://github.com/protocolbuffers/protobuf-go/tree/master/testing/protocmp)\npackage is also particularly helpful when working with Protobuf types.\n\nHere is an example of comparing two slices, but using a custom Option to sort\nthe slices so that the order of elements does not matter.\n\n```go\na := []int{3, 5, 1, 6, 7}\nb := []int{1, 7, 6, 3, 5}\nmust.Eq(t, a, b, must.Cmp(cmpopts.SortSlices(func(i, j int) bool {\n  return i \u003c j\n})))\n```\n\n### PostScripts\n\nSome tests are large and complex (like e2e testing). It can be helpful to provide more context\non test case failures beyond the actual assertion. Logging could do this, but often we want to\nonly produce output on failure.\n\nThe `test` and `must` packages provide a `PostScript` interface which can be implemented to\nadd more context in the output of failed tests. There are handy implementations of the `PostScript`\ninterface provided - `Sprint`, `Sprintf`, `Values`, and `Func`.\n\nBy adding one or more `PostScript` to an assertion, on failure the error message will be appended\nwith the additional context.\n\n```golang\n// Add a single Sprintf-string to the output of a failed test assertion.\nmust.Eq(t, exp, result, must.Sprintf(\"some more context: %v\", value))\n```\n\n```golang\n// Add a formatted key-value map to the output of a failed test assertion.\nmust.Eq(t, exp, result, must.Values(\n  \"one\", 1,\n  \"two\", 2,\n  \"fruit\", \"banana\",\n))\n```\n\n```golang\n// Add the output from a closure to the output of a failed test assertion.\nmust.Eq(t, exp, result, must.Func(func() string {\n  // ... something interesting\n  return s\n})\n```\n\n### Skip\n\nSometimes it makes sense to just skip running a certain test case. Maybe the\noperating system is incompatible or a certain required command is not installed.\nThe `skip` package provides utilities for skipping tests under some given\nconditions.\n\n\n```go\nskip.OperatingSystem(t, \"windows\", \"plan9\", \"dragonfly\")\n```\n\n```go\nskip.NotArchitecture(t, \"amd64\", \"arm64\")\n```\n\n```go\nskip.CommandUnavailable(t, \"java\")\n```\n\n```go\nskip.EnvironmentVariableSet(t, \"CI\")\n```\n\n### Util\n\nHow often have you written a helper method for writing a temporary file in unit\ntests? With the `util` package, that boilerplate is resolved once and for all.\n\n```go\npath := util.TempFile(t,\n  util.Mode(0o644),\n  util.String(\"some content!\"),\n)\n```\n\nThe file referenced by `path` will be cleaned up automatically at the end of\nthe test run, similar to `t.TempDir()`.\n\n### Wait\n\nSometimes a test needs to wait on a condition for a non-deterministic amount of time.\nFor these cases, the `wait` package provides utilities for configuring conditionals\nthat can assert some condition becomes true, or that some condition remains true -\nwhether for a specified amount time, or a specific number of iterations.\n\nA `Constraint` is created in one of two forms\n\n- `InitialSuccess` - assert a function eventually returns a positive result\n- `ContinualSuccess` - assert a function continually returns a positive result\n\nA `Constraint` may be configured with a few Option functions.\n\n- `Timeout` - set a time bound on the constraint\n- `Attempts` - set an iteration bound on the constraint\n- `Gap` - set the iteration interval pace\n- `BoolFunc` - set a predicate function of type `func() bool`\n- `ErrorFunc` - set a predicate function of type `func() error`\n- `TestFunc` - set a predicate function of type `func() (bool, error)`\n\n#### Assertions form\n\nThe `test` and `must` package implement an assertion helper for using the `wait` package.\n\n```go\nmust.Wait(t, wait.InitialSuccess(wait.ErrorFunc(f)))\n```\n\n```go\nmust.Wait(t, wait.ContinualSuccess(\n    wait.ErrorFunc(f),\n    wait.Attempts(100),\n    wait.Gap(10 * time.Millisecond),\n))\n```\n\n#### Fundamental form\n\nAlthough the 99% use case is via the `test` or `must` packages as described above,\nthe `wait` package can also be used in isolation by calling `Run()` directly. An\nerror is returned if the conditional failed, and nil otherwise.\n\n```go\nc := wait.InitialSuccess(\n    BoolFunc(f),\n    Timeout(10 * time.Seconds),\n    Gap(1 * time.Second),\n)\nerr := c.Run()\n```\n\n### Examples (equality)\n\n```go\nimport \"github.com/shoenig/test/must\"\n\n// ...\n\ne1 := Employee{ID: 100, Name: \"Alice\"}\ne2 := Employee{ID: 101, Name: \"Bob\"}\n\n// using cmp.Equal (like magic!)\nmust.Eq(t, e1, e2)\n\n// using == operator\nmust.EqOp(t, e1, e2)\n\n// using a custom comparator\nmust.EqFunc(t, e1, e2, func(a, b *Employee) bool {\n    return a.ID == b.ID\n})\n\n// using .Equal method\nmust.Equal(t, e1, e2)\n```\n\n### Output\n\nThe `test` and `must` package attempt to create useful, readable output when an assertion goes awry. Some random examples below.\n\n```text\ntest_test.go:779: expected different file permissions\n↪ name: find\n↪ exp: -rw-rwx-wx\n↪ got: -rwxr-xr-x\n```\n\n```text\ntests_test.go:569: expected maps of same values via 'eq' function\n↪ difference:\nmap[int]test.Person{\n0: {ID: 100, Name: \"Alice\"},\n  \t1: {\n  \t\tID:   101,\n-  \t\tName: \"Bob\",\n+  \t\tName: \"Bob B.\",\n    \t},\n    }\n```\n\n```text\ntest_test.go:520: expected slice[1].Less(slice[2])\n↪ slice[1]: \u0026{200 Bob}\n↪ slice[2]: \u0026{150 Carl}\n```\n\n```text\ntest_test.go:688: expected maps of same values via .Equal method\n↪ differential ↷\n  map[int]*test.Person{\n  \t0: \u0026{ID: 100, Name: \"Alice\"},\n  \t1: \u0026{\n- \t\tID:   101,\n+ \t\tID:   200,\n  \t\tName: \"Bob\",\n  \t},\n  }\n```\n\n```text\ntest_test.go:801: expected regexp match\n↪ s: abcX\n↪ re: abc\\d\n```\n\n### License\n\nOpen source under the [MPL](LICENSE)\n","funding_links":[],"categories":["Go"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshoenig%2Ftest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshoenig%2Ftest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshoenig%2Ftest/lists"}