{"id":16849532,"url":"https://github.com/poy/onpar","last_synced_at":"2025-08-08T05:11:12.051Z","repository":{"id":12799432,"uuid":"72848106","full_name":"poy/onpar","owner":"poy","description":"Parallel testing framework for Go","archived":false,"fork":false,"pushed_at":"2025-05-09T02:14:38.000Z","size":215,"stargazers_count":39,"open_issues_count":2,"forks_count":8,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-09T03:26:52.164Z","etag":null,"topics":["go","parallel","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/poy.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":"2016-11-04T13:01:18.000Z","updated_at":"2025-05-09T02:14:22.000Z","dependencies_parsed_at":"2024-03-12T21:43:31.087Z","dependency_job_id":"0a446cc7-7e9b-450f-8740-cb001e0aa6fd","html_url":"https://github.com/poy/onpar","commit_stats":{"total_commits":111,"total_committers":9,"mean_commits":"12.333333333333334","dds":0.6666666666666667,"last_synced_commit":"e21b4b09d6858721b98b27c91b3ac815841d921c"},"previous_names":["apoydence/onpar"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/poy/onpar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poy%2Fonpar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poy%2Fonpar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poy%2Fonpar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poy%2Fonpar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/poy","download_url":"https://codeload.github.com/poy/onpar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poy%2Fonpar/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269366853,"owners_count":24405250,"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","status":"online","status_checked_at":"2025-08-08T02:00:09.200Z","response_time":72,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","parallel","testing"],"created_at":"2024-10-13T13:16:15.628Z","updated_at":"2025-08-08T05:11:12.038Z","avatar_url":"https://github.com/poy.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# onpar\n[![docs][pkg-docs-badge]][pkg-docs] [![gha][gha-badge]][gha]\n\nParallel testing framework for Go\n\n## Quick Start\n\n1. Add the dependency: `go get github.com/poy/onpar@latest`\n2. Write a test:\n\n``` go\nfunc TestFoo(t *testing.T) {\n    type testCtx struct {\n        // Embedding *testing.T allows us to pass the testCtx directly to most\n        // assertion libraries.\n        *testing.T\n\n        someVal somepkg.SomeType\n    }\n    o := onpar.BeforeEach(onpar.New(t), func(t *testing.T) testCtx {\n        return testCtx{\n            T: t,\n            someVal: somepkg.NewSomeType(),\n        }\n    })\n    defer o.Run()\n\n    o.Spec(\"a test case\", func(t testCtx) {\n        // We shadow the \"t\" variable to prevent child tests from asserting\n        // on the parent t, just like common t.Run semantics.\n        if t.someVal.SomeMethod() != anExpectation {\n            t.Fatalf(\"expected %q to equal %q\", t.someVal.SomeMethod(), anExpectation)\n        }\n    })\n}\n```\n\n3. Run the test: `$ go test`\n\n## Goals\n\n- Provide structured testing, with per-spec setup and teardown.\n- Discourage using closure state to share memory between setup/spec/teardown\n  functions.\n  - Sharing memory between the steps of a spec by using closure state means that\n    you're also sharing memory with _other tests_. This often results in test\n    pollution.\n- Run tests in parallel by default.\n  - Most of the time, well-written unit tests are perfectly capable of running\n    in parallel, and sometimes running tests in parallel can uncover extra bugs.\n    In onpar, this is the default.\n- Work within standard go test functions, simply wrapping standard `t.Run`\n  semantics.\n  - `onpar` should not feel utterly alien to people used to standard go testing.\n    It does some extra work to allow structured tests, but for the most part it\n    isn't hiding any complicated logic - it mostly just calls the beforeeach,\n    spec, and aftereach in `t.Run`.\n\nOnpar provides a BDD style of testing, similar to what you might find with\nsomething like ginkgo or goconvey. The biggest difference between onpar and its\npeers is that a `BeforeEach` function in `onpar` must return a value, and that\nvalue is the parameter required in child calls to `Spec`, `AfterEach`, and\n`BeforeEach`.\n\nThis allows you to write tests that share memory between `BeforeEach`, `Spec`,\nand `AfterEach` functions _without sharing memory with other tests_. When used\nproperly, this makes test pollution nearly impossible and makes it harder to\nwrite flaky tests.\n\n## Running\n\nAfter constructing a top-level `*Onpar`, `defer o.Run()`.\n\nIf `o.Run()` is never called, the test will panic during `t.Cleanup`. This is to\nprevent false passes when `o.Run()` is accidentally omitted.\n\n### Assertions\nOnPar provides an expectation library in the `expect` sub-package. Here is some\nmore information about `Expect` and some of the matchers that are available:\n\n- [Expect](expect/README.md)\n- [Matchers](matchers/README.md)\n\nHowever, OnPar is not opinionated - any assertion library or framework may be\nused within specs.\n\n### Specs\n\nTest assertions are done within a `Spec()` function. Each `Spec` has a name and\na function with a single argument. The type of the argument is determined by how\nthe suite was constructed: `New()` returns a suite that takes a `*testing.T`,\nwhile `BeforeEach` constructs a suite that takes the return type of the setup\nfunction.\n\nEach `Spec` is run in parallel (`t.Parallel()` is invoked for each spec before\ncalling the given function).\n\n```go\nfunc TestSpecs(t *testing.T) {\n    type testContext struct {\n        t *testing.T\n        a int\n        b float64\n    }\n\n    o := onpar.BeforeEach(onpar.New(t), func(t *testing.T) testContext {\n        return testContext{t: t, a: 99, b: 101.0}\n    })\n    defer o.Run()\n\n    o.AfterEach(func(tt testContext) {\n            // ...\n    })\n\n    o.Spec(\"something informative\", func(tt testContext) {\n        if tt.a != 99 {\n            tt.t.Errorf(\"%d != 99\", tt.a)\n        }\n    })\n}\n```\n\n### Serial Specs\n\nWhile `onpar` is intended to heavily encourage running specs in parallel, we\nrecognize that that's not always an option. Sometimes proper mocking is just too\ntime consuming, or a singleton package is just too hard to replace with\nsomething better.\n\nFor those times that you just can't get around the need for serial tests, we\nprovide `SerialSpec`. It works exactly the same as `Spec`, except that onpar\ndoesn't call `t.Parallel` before running it.\n\n### Grouping\n\n`Group`s are used to keep `Spec`s in logical place. The intention is to gather\neach `Spec` in a reasonable place. Each `Group` may construct a new child suite\nusing `BeforeEach`.\n\n\n```go\nfunc TestGrouping(t *testing.T) {\n    type topContext struct {\n        t *testing.T\n        a int\n        b float64\n    }\n\n    o := onpar.BeforeEach(onpar.New(t), func(t *testing.T) topContext {\n        return topContext{t: t, a: 99, b: 101}\n    }\n    defer o.Run()\n\n    o.Group(\"some-group\", func() {\n        type groupContext struct {\n            t *testing.T\n            s string\n        }\n        o := onpar.BeforeEach(o, func(tt topContext) groupContext {\n            return groupContext{t: tt.t, s: \"foo\"}\n        })\n\n        o.AfterEach(func(tt groupContext) {\n            // ...\n        })\n\n        o.Spec(\"something informative\", func(tt groupContext) {\n            // ...\n        })\n    })\n}\n```\n\n### Run Order\n\nEach `BeforeEach()` runs before any `Spec` in the same `Group`. It will also run\nbefore any sub-group `Spec`s and their `BeforeEach`es. Any `AfterEach()` will\nrun after the `Spec` and before parent `AfterEach`es.\n\n``` go\nfunc TestRunOrder(t *testing.T) {\n    type topContext struct {\n        t *testing.T\n        i int\n        s string\n    }\n    o := onpar.BeforeEach(onpar.New(t), func(t *testing.T) topContext {\n        // Spec \"A\": Order = 1\n        // Spec \"B\": Order = 1\n        // Spec \"C\": Order = 1\n        return topContext{t: t, i: 99, s: \"foo\"}\n    })\n    defer o.Run()\n\n    o.AfterEach(func(tt topContext) {\n        // Spec \"A\": Order = 4\n        // Spec \"B\": Order = 6\n        // Spec \"C\": Order = 6\n    })\n\n    o.Group(\"DA\", func() {\n        o.AfterEach(func(tt topContext) {\n            // Spec \"A\": Order = 3\n            // Spec \"B\": Order = 5\n            // Spec \"C\": Order = 5\n        })\n\n        o.Spec(\"A\", func(tt topContext) {\n            // Spec \"A\": Order = 2\n        })\n\n        o.Group(\"DB\", func() {\n            type dbContext struct {\n                t *testing.T\n                f float64\n            }\n            o := onpar.BeforeEach(o, func(tt topContext) dbContext {\n                // Spec \"B\": Order = 2\n                // Spec \"C\": Order = 2\n                return dbContext{t: tt.t, f: 101}\n            })\n\n            o.AfterEach(func(tt dbContext) {\n                // Spec \"B\": Order = 4\n                // Spec \"C\": Order = 4\n            })\n\n            o.Spec(\"B\", func(tt dbContext) {\n                // Spec \"B\": Order = 3\n            })\n\n            o.Spec(\"C\", func(tt dbContext) {\n                // Spec \"C\": Order = 3\n            })\n        })\n\n        o.Group(\"DC\", func() {\n            o := onpar.BeforeEach(o, func(tt topContext) *testing.T {\n                // Will not be invoked (there are no specs)\n            })\n\n            o.AfterEach(func(t *testing.T) {\n                // Will not be invoked (there are no specs)\n            })\n        })\n    })\n}\n```\n\n## Avoiding Closure\n\nWhy bother with returning values from a `BeforeEach`? To avoid closure of\ncourse! When running `Spec`s in parallel (which they always do), each variable\nneeds a new instance to avoid race conditions. If you use closure, then this\ngets tough. So onpar will pass the arguments to the given function returned by\nthe `BeforeEach`.\n\nThe `BeforeEach` is a gatekeeper for arguments. The returned values from\n`BeforeEach` are required for the following `Spec`s. Child `Group`s are also\npassed what their direct parent `BeforeEach` returns.\n\n[pkg-docs-badge]:             https://pkg.go.dev/badge/github.com/poy/onpar.svg\n[pkg-docs]:                   https://pkg.go.dev/github.com/poy/onpar\n[gha-badge]:                  https://github.com/poy/onpar/actions/workflows/unit-test.yml/badge.svg\n[gha]:                        https://github.com/poy/onpar/actions/workflows/unit-test.yml\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpoy%2Fonpar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpoy%2Fonpar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpoy%2Fonpar/lists"}