Skip to content

Commit 3cad16d

Browse files
authored
Update README.md
1 parent 455793b commit 3cad16d

File tree

1 file changed

+105
-12
lines changed

1 file changed

+105
-12
lines changed

README.md

+105-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,111 @@
1-
# eventually
1+
# Eventually
2+
3+
[![PkgGoDev](https://pkg.go.dev/badge/github.com/rentziass/eventually)](https://pkg.go.dev/github.com/rentziass/eventually)
4+
5+
Eventually provides support for running a test block that should eventually succeed, while still
6+
providing access to a `testing.TB` throughout the whole block. Eventually will keep trying re-running
7+
the test block until either a timeout or max attempts are reached. While this was created to test
8+
asynchronous systems I suppose it might help with flaky tests too :D. Here's an example:
29

310
```go
4-
// Must will keep trying until it succeeds or the test times out or reaches
5-
// max attempts. If it fails, it will call t.Fatalf.
6-
eventually.Must(t, func(t testing.TB) {
7-
t.Fatal("but keep trying")
8-
}, eventually.WithMaxAttempts(10))
11+
func TestAsync(t *testing.T) {
12+
asyncProcessSucceeded := false
13+
14+
go func() {
15+
time.Sleep(100 * time.Millisecond)
16+
asyncProcessSucceeded = true
17+
}()
18+
19+
eventually.Should(t, func(t testing.TB) {
20+
if !asyncProcessSucceeded {
21+
t.Fail()
22+
}
23+
})
24+
}
25+
```
26+
27+
Notice how within the function you pass to `eventually.Should` you have access to a `t testing.TB`, this
28+
allows you to still use your helpers and assertions that rely on the good old `t`. Here's another example,
29+
using [testify](https://github.com/stretchr/testify)'s `assert` and `require`:
30+
31+
```go
32+
// code that sets up async consequences, e.g. writes some events on a queue
33+
34+
eventually.Should(t, func(t testing.TB) {
35+
events, err := readFromQueue()
36+
require.NoError(t, err)
37+
require.Len(t, events, 1)
38+
assert.Equal(t, "event", events[0])
39+
})
40+
41+
```
42+
43+
Eventually has [`Should`](https://pkg.go.dev/github.com/rentziass/eventually#Should) and [`Must`](https://pkg.go.dev/github.com/rentziass/eventually#Must) functions, that
44+
correspond to [`Fail`](https://pkg.go.dev/testing#T.Fail) and [`FailNow`](https://pkg.go.dev/testing#T.FailNow) respectively in case of failure.
45+
46+
Behaviour can be customised with use of [`Options`](https://pkg.go.dev/github.com/rentziass/eventually#Option), for example:
47+
48+
```go
49+
eventually.Should(t, func(t testing.TB) {
50+
// your test code here
51+
},
52+
eventually.WithTimeout(10*time.Second),
53+
eventually.WithInterval(100*time.Millisecond),
54+
eventually.WithMaxAttempts(10),
55+
)
56+
```
57+
58+
And if you want to reuse your configuration you can do so by creating your very own `Eventually`. The example above would look something like:
59+
60+
```go
61+
eventually := eventually.New(
62+
eventually.WithTimeout(10*time.Second),
63+
eventually.WithInterval(100*time.Millisecond),
64+
eventually.WithMaxAttempts(10),
65+
)
966

10-
// Should will keep trying until it succeeds or the test times out or reaches
11-
// max attempts. If it fails, it will call t.Errorf.
1267
eventually.Should(t, func(t testing.TB) {
13-
t.Fatal("but keep trying")
14-
}, eventually.WithTimeout(10*time.Second), eventually.WithInterval(1*time.Second))
68+
// test code
69+
})
70+
71+
eventually.Must(t, func(t testing.TB) {
72+
// test code
73+
})
74+
```
75+
76+
## Why does this exist?
77+
78+
> TL;DR: I like `t` **a lot**
79+
80+
Other testing libraries have solutions for this. Testify for instance has its own [`Eventually`](https://pkg.go.dev/github.com/stretchr/[email protected]/assert#Eventually), but
81+
the function it takes returns a `bool` and has no access to an "inner" `*testing.T` to be used for helpers and assertions.
82+
Let'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
83+
it can't find the file (more convenient than handling all errors in the test itself). If the file you want to test is being
84+
created asynchronously using that helper within Eventually will halt the whole test instead of trying executing again. In Go code:
85+
86+
```go
87+
func TestAsyncFile(t *testing.T) {
88+
// setup
89+
90+
assert.Eventually(t, func() bool {
91+
contents := readFile(t, "path") // <-- this halts the whole TestAsyncFile, not just this Eventually run
92+
return contents == "expected"
93+
})
94+
}
95+
96+
func readFile(t *testing.T, path string) string {
97+
f, err := os.Open(path)
98+
require.NoError(t, err)
99+
100+
// reading the file
101+
}
15102
```
16103

17-
## TODO
18-
- [ ] Add documentation
104+
Another 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:
105+
106+
```go
107+
gomega.Eventually(func(g gomega.Gomega) {
108+
contents := readFile(t, "path") // no t :(
109+
g.Expect(contents).To(gomega.Equal("expected"))
110+
}).Should(gomega.Succeed())
111+
```

0 commit comments

Comments
 (0)