{"id":15750125,"url":"https://github.com/rentziass/eventually","last_synced_at":"2025-03-13T14:32:44.255Z","repository":{"id":145221578,"uuid":"617114752","full_name":"rentziass/eventually","owner":"rentziass","description":"Keep retrying a bit of Go test code that you know _should_ pass, eventually, idiomatically.","archived":false,"fork":false,"pushed_at":"2024-06-07T14:47:55.000Z","size":15,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-26T20:11:49.015Z","etag":null,"topics":["go","golang","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/rentziass.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-03-21T18:19:27.000Z","updated_at":"2024-06-07T14:47:59.000Z","dependencies_parsed_at":"2024-01-24T13:26:42.608Z","dependency_job_id":"32b57402-0ae6-4aee-8873-e8fc5f7589db","html_url":"https://github.com/rentziass/eventually","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rentziass%2Feventually","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rentziass%2Feventually/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rentziass%2Feventually/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rentziass%2Feventually/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rentziass","download_url":"https://codeload.github.com/rentziass/eventually/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243422797,"owners_count":20288523,"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","golang","testing"],"created_at":"2024-10-04T06:22:15.743Z","updated_at":"2025-03-13T14:32:39.241Z","avatar_url":"https://github.com/rentziass.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Eventually\n\n[![PkgGoDev](https://pkg.go.dev/badge/github.com/rentziass/eventually)](https://pkg.go.dev/github.com/rentziass/eventually)\n\nEventually provides support for running a test block that should eventually succeed, while still\nproviding access to a `testing.TB` throughout the whole block. Eventually will keep trying re-running\nthe test block until either a timeout or max attempts are reached. While this was created to test\nasynchronous systems I suppose it might help with flaky tests too :D. Here's an example:\n\n```go\nfunc TestAsync(t *testing.T) {\n  asyncProcessSucceeded := false\n\n  go func() {\n    time.Sleep(100 * time.Millisecond)\n    asyncProcessSucceeded = true\n  }()\n\n  eventually.Should(t, func(t testing.TB) {\n    if !asyncProcessSucceeded {\n      t.Fail()\n    }\n  })\n}\n```\n\nNotice how within the function you pass to `eventually.Should` you have access to a `t testing.TB`, this\nallows you to still use your helpers and assertions that rely on the good old `t`. Here's another example,\nusing [testify](https://github.com/stretchr/testify)'s `assert` and `require`:\n\n```go\n// code that sets up async consequences, e.g. writes some events on a queue\n\neventually.Should(t, func(t testing.TB) {\n  events, err := readFromQueue()\n  require.NoError(t, err)\n  require.Len(t, events, 1)\n  assert.Equal(t, \"event\", events[0])\n})\n\n```\n\nEventually has [`Should`](https://pkg.go.dev/github.com/rentziass/eventually#Should) and [`Must`](https://pkg.go.dev/github.com/rentziass/eventually#Must) functions, that\ncorrespond to [`Fail`](https://pkg.go.dev/testing#T.Fail) and [`FailNow`](https://pkg.go.dev/testing#T.FailNow) respectively in case of failure.\n\nBehaviour can be customised with use of [`Options`](https://pkg.go.dev/github.com/rentziass/eventually#Option), for example:\n\n```go\neventually.Should(t, func(t testing.TB) {\n  // your test code here\n},\n  eventually.WithTimeout(10*time.Second),\n  eventually.WithInterval(100*time.Millisecond),\n  eventually.WithMaxAttempts(10),\n)\n```\n\nAnd if you want to reuse your configuration you can do so by creating your very own `Eventually`. The example above would look something like:\n\n```go\neventually := eventually.New(\n  eventually.WithTimeout(10*time.Second),\n  eventually.WithInterval(100*time.Millisecond),\n  eventually.WithMaxAttempts(10),\n)\n\neventually.Should(t, func(t testing.TB) {\n  // test code\n})\n\neventually.Must(t, func(t testing.TB) {\n  // test code\n})\n```\n\n## Why does this exist?\n\n\u003e TL;DR: I like `t` **a lot**\n\nOther testing libraries have solutions for this. Testify for instance has its own [`Eventually`](https://pkg.go.dev/github.com/stretchr/testify@v1.8.2/assert#Eventually), but\nthe function it takes returns a `bool` and has no access to an \"inner\" `*testing.T` to be used for helpers and assertions.\nLet's say for example that you have a helper function that reads a file and returns its content as a string, failing the test if\nit can't find the file (more convenient than handling all errors in the test itself). If the file you want to test is being\ncreated asynchronously using that helper within Eventually will halt the whole test instead of trying executing again. In Go code:\n\n```go\nfunc TestAsyncFile(t *testing.T) {\n  // setup\n\n  assert.Eventually(t, func() bool {\n    contents := readFile(t, \"path\") // \u003c-- this halts the whole TestAsyncFile, not just this Eventually run\n    return contents == \"expected\"\n  })\n}\n\nfunc readFile(t *testing.T, path string) string {\n  f, err := os.Open(path)\n  require.NoError(t, err)\n\n  // reading the file\n}\n```\n\nWith the addition of [`assert.EventuallyWithT`](https://pkg.go.dev/github.com/stretchr/testify/assert#EventuallyWithT)\nit would seem that our problem is solved, but the issue with that is that the\n[`*CollectT`](https://pkg.go.dev/github.com/stretchr/testify/assert#CollectT)\navailable to the function [panics on FailNow](https://pkg.go.dev/github.com/stretchr/testify/assert#CollectT.FailNow) (which is what `t.FailNow` does). This means that if you have anything using `FailNow` (including using testify's own `require`) this will panic and halt the whole test, not just the `EventuallyWithT` block:\n\n```go\nfunc TestAsyncFile(t *testing.T) {\n  // setup\n\n  assert.EventuallyWithT(t, func(t *assert.Assertions) {\n    f, err := os.Open(path)\n    require.NoError(t, err) // \u003c-- this would panic and halt the whole test\n\n    // your test code here\n  })\n}\n\nAnother available alternative is Gomega's [`Eventually`](https://pkg.go.dev/github.com/onsi/gomega#Eventually) (yes, this package has a very original name), which can be very convenient to use but requires buying into Gomega as a whole, which is quite the commitment (and I don't find a particularly idiomatic way of writing tests in Go but hey, opinions). This also still doesn't give access to a `t` with its own scope, you can do assertions within the `Eventually` block but if you have code that relies on `*testing.T` being around you cannot use it:\n\n```go\ngomega.Eventually(func(g gomega.Gomega) {\n  contents := readFile(t, \"path\") // no t :(\n  g.Expect(contents).To(gomega.Equal(\"expected\"))\n}).Should(gomega.Succeed())\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frentziass%2Feventually","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frentziass%2Feventually","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frentziass%2Feventually/lists"}