Skip to content

Commit 75b71da

Browse files
authored
Merge pull request #9 from go-tstr/improve-readme
Update docs
2 parents bc63652 + f049dac commit 75b71da

File tree

9 files changed

+210
-24
lines changed

9 files changed

+210
-24
lines changed

README.md

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,172 @@
1-
[![Go Reference](https://pkg.go.dev/badge/github.com/go-tstr/tstr.svg)](https://pkg.go.dev/github.com/go-tstr/tstr) [![codecov](https://codecov.io/github/go-tstr/tstr/graph/badge.svg?token=H3u7Ui9PfC)](https://codecov.io/github/go-tstr/tstr)
1+
[![Go Reference](https://pkg.go.dev/badge/github.com/go-tstr/tstr.svg)](https://pkg.go.dev/github.com/go-tstr/tstr) [![codecov](https://codecov.io/github/go-tstr/tstr/graph/badge.svg?token=H3u7Ui9PfC)](https://codecov.io/github/go-tstr/tstr) ![main](https://github.com/go-tstr/tstr/actions/workflows/go.yml/badge.svg?branch=main)
22

3-
# Better testing with TSTR!
3+
# TSTR: your ultimate testing library!
4+
5+
tstr is testing library allows you to write integration and black-box tests like normal unit tests in Go.
6+
7+
You can declare the test dependencies like:
8+
9+
- compose files
10+
- single containers
11+
- cli commands
12+
- main package of Go program
13+
14+
and let tstr take care of the rest.
15+
16+
## Usage
17+
18+
This library is build on top of two concepts:
19+
20+
- tstr.Tester
21+
- tstr.Dependency
22+
23+
### tstr.Tester
24+
25+
There's two common ways to use tester, either from `func TestMain` or from `func TestXXX`. For both of these approaches there's helper function; `tstr.RunMain` and `tstr.Run`, which make it easy to setup and run `tstr.Tester`.
26+
27+
#### tstr.RunMain
28+
29+
```go
30+
func TestMain(m *testing.M) {
31+
tstr.RunMain(m, tstr.WithDeps(
32+
// Pass test dependencies here.
33+
))
34+
}
35+
```
36+
37+
With `TestMain` approach you will have single test env within the packge.
38+
`tstr.RunMain` will setup the test env you defined, call `m.Run()`, cleanup test env and finally call `os.Exit` with returned exit code.
39+
40+
#### tstr.Run
41+
42+
This approach allows more granular control over test env. For example you can have single test env for each top level test. This can be usefull when you want to avoid any side effects and shared state between tests. Also this approach allows more advaced usage like creating a pool of test envs for parallel testing.
43+
44+
##### tstr.WithFn
45+
46+
Simplest way to use `tstr.Run` is with the `tstr.WithFn` option:
47+
48+
```go
49+
func TestMyFunc(t *testing.T) {
50+
err := tstr.Run(
51+
tstr.WithDeps(
52+
// Pass test dependencies here.
53+
),
54+
tstr.WithFn(func() {
55+
const (
56+
input = 1
57+
expected = 1
58+
)
59+
got := MyFunc(input)
60+
assert.Equal(t, expected, got)
61+
}),
62+
)
63+
require.NoError(t, err)
64+
}
65+
```
66+
67+
##### tstr.WithTable
68+
69+
For table driven tests you can use `tstr.WithTable` which loops over the given test table and executes test function for each element using `t.Run`:
70+
71+
```go
72+
func TestMyFunc(t *testing.T) {
73+
type test struct {
74+
Name string
75+
input int
76+
expected int
77+
}
78+
79+
tests := []test{
80+
{Name: "test-1", input: 1, expected: 1},
81+
{Name: "test-2", input: 2, expected: 2},
82+
{Name: "test-3", input: 3, expected: 3},
83+
}
84+
85+
err := tstr.Run(
86+
tstr.WithDeps(
87+
// Add dependencies here.
88+
),
89+
tstr.WithTable(t, tests, func(t *testing.T, tt test) {
90+
got := MyFunc(tt.input)
91+
assert.Equal(t, tt.expected, got)
92+
}),
93+
)
94+
require.NoError(t, err)
95+
}
96+
```
97+
98+
### tstr.Dependency
99+
100+
`tstr.Dependency` declares an interface for test dependency which can be then controlled by `tstr.Tester`. This repo provides the most commonly used dependecies that user can use within their tests. Since `tstr.Dependency` is just an interface users can also implement their own custom dependencies.
101+
102+
#### Compose
103+
104+
Compose dependecy allows you to define and manage Docker Compose stacks as test dependencies. You can create a Compose stack from projects compose file and control its lifecycle within your tests.
105+
106+
```go
107+
func TestMain(m *testing.M) {
108+
tstr.RunMain(m, tstr.WithDeps(
109+
compose.New(
110+
compose.WithFile("../docker-compose.yaml"),
111+
compose.WithUpOptions(tc.Wait(true)),
112+
compose.WithDownOptions(tc.RemoveVolumes(true)),
113+
compose.WithEnv(map[string]string{"DB_PORT": "5432"}),
114+
compose.WithWaitForService("postgres", wait.ForLog("ready to accept connections")),
115+
),
116+
))
117+
}
118+
```
119+
120+
#### Container
121+
122+
Container dependecy allows you to define and manage single containers as test dependencies. You can use predefined modules from testcontainer-go or create generic container.
123+
124+
```go
125+
func TestMain(m *testing.M) {
126+
tstr.RunMain(m, tstr.WithDeps(
127+
container.New(
128+
container.WithModule(postgres.Run, "postgres:16-alpine",
129+
postgres.WithDatabase("test"),
130+
postgres.WithUsername("user"),
131+
postgres.WithPassword("password"),
132+
),
133+
),
134+
))
135+
}
136+
```
137+
138+
#### Cmd
139+
140+
Cmd dependecy is the most versatile one. It can be used for running any binary or even compiling a Go application and running it as dependency.
141+
142+
This example compiles `my-app` Go application, instruments it for coverage collections, waits for it to be ready and finally starts running tests.
143+
144+
```go
145+
func TestMain(m *testing.M) {
146+
tstr.RunMain(m, tstr.WithDeps(
147+
cmd.New(
148+
cmd.WithGoCode("../", "./cmd/my-app"),
149+
cmd.WithReadyHTTP("http://localhost:8080/ready"),
150+
cmd.WithEnvAppend("GOCOVERDIR=./cover"),
151+
),
152+
))
153+
}
154+
```
155+
156+
#### Custom Dependencies
157+
158+
You can also create your own custom dependencies by implementing the `tstr.Dependency` interface.
159+
160+
```go
161+
type Custom struct{}
162+
163+
func New() *Custom {
164+
return &Custom{}
165+
}
166+
167+
func (c *Custom) Start() error { return nil }
168+
169+
func (c *Custom) Ready() error { return nil }
170+
171+
func (c *Custom) Stop() error { return nil }
172+
```

dep/cmd/cmd.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bufio"
55
"errors"
66
"fmt"
7+
"net/http"
78
"os"
89
"os/exec"
910
"regexp"
@@ -102,6 +103,28 @@ func WithReadyFn(fn func(*exec.Cmd) error) Opt {
102103
}
103104
}
104105

106+
// WithReadyHTTP sets the ready function to wait for url to return 200 OK.
107+
func WithReadyHTTP(url string) Opt {
108+
return func(c *Cmd) error {
109+
c.ready = func(cmd *exec.Cmd) error {
110+
client := &http.Client{
111+
Timeout: 10 * time.Second,
112+
}
113+
for {
114+
resp, err := client.Get(url)
115+
if err != nil {
116+
continue
117+
}
118+
if resp.StatusCode == http.StatusOK {
119+
return nil
120+
}
121+
time.Sleep(100 * time.Millisecond)
122+
}
123+
}
124+
return nil
125+
}
126+
}
127+
105128
// WithStopFn allows user to provide custom stop function.
106129
func WithStopFn(fn func(*exec.Cmd) error) Opt {
107130
return func(c *Cmd) error {
@@ -186,6 +209,7 @@ func WithPreCmd(cmd *exec.Cmd) Opt {
186209

187210
// WithGoCode builds the given Go projects and sets the main package as the command.
188211
// By default the command is set to collect coverage data.
212+
// Working directory for build command is set to modulePath which means that the mainPkg should be relative to it.
189213
func WithGoCode(modulePath, mainPkg string) Opt {
190214
return func(c *Cmd) error {
191215
dir, err := os.MkdirTemp("", "go-tstr")

dep/compose/compose_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ func TestCompose(t *testing.T) {
2020

2121
tests := []struct {
2222
name string
23-
fn func() error
2423
compose *compose.Compose
2524
err error
2625
}{
@@ -55,7 +54,7 @@ func TestCompose(t *testing.T) {
5554

5655
for _, tt := range tests {
5756
t.Run(tt.name, func(t *testing.T) {
58-
deptest.ErrorIs(t, tt.compose, tt.fn, tt.err)
57+
deptest.ErrorIs(t, tt.compose, nil, tt.err)
5958
})
6059
}
6160
}

dep/container/container.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func (c *Container) Ready() error {
4444
func (c *Container) Stop() error {
4545
return testcontainers.TerminateContainer(c.c)
4646
}
47+
4748
func WithReadyFn(fn func(testcontainers.Container) error) Opt {
4849
return func(c *Container) error {
4950
c.ready = fn

dep/container/container_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
func TestContainer(t *testing.T) {
1515
tests := []struct {
1616
name string
17-
fn func() error
1817
container *container.Container
1918
err error
2019
}{
@@ -76,7 +75,7 @@ func TestContainer(t *testing.T) {
7675

7776
for _, tt := range tests {
7877
t.Run(tt.name, func(t *testing.T) {
79-
deptest.ErrorIs(t, tt.container, tt.fn, tt.err)
78+
deptest.ErrorIs(t, tt.container, nil, tt.err)
8079
})
8180
}
8281
}

dep/deptest/dep.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ import (
88
)
99

1010
// ErrorIs is a convinience wrapper around tstr.Run that can be used to test single dependency.
11-
func ErrorIs(t *testing.T, d tstr.Dependency, fn func() error, err error) bool {
11+
func ErrorIs(t *testing.T, d tstr.Dependency, fn func(), err error) bool {
1212
return assert.ErrorIs(t, tstr.Run(
1313
tstr.WithDeps(d),
14-
tstr.WithFn(func() error {
15-
if fn == nil {
16-
return nil
14+
tstr.WithFn(func() {
15+
if fn != nil {
16+
fn()
1717
}
18-
return fn()
1918
}),
2019
), err)
2120
}

dep/deptest/dep_test.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
11
package deptest_test
22

33
import (
4-
"errors"
5-
"fmt"
64
"testing"
75

86
"github.com/go-tstr/tstr/dep/deptest"
97
"github.com/stretchr/testify/assert"
108
)
119

1210
func TestErrorIs_NilErr(t *testing.T) {
13-
got := deptest.ErrorIs(t, MockDep{}, func() error { return nil }, nil)
14-
assert.True(t, got)
15-
}
16-
17-
func TestErrorIs_Err(t *testing.T) {
18-
err := errors.New("error")
19-
got := deptest.ErrorIs(t, MockDep{}, func() error { return fmt.Errorf("wrapped: %w", err) }, err)
11+
got := deptest.ErrorIs(t, MockDep{}, func() {}, nil)
2012
assert.True(t, got)
2113
}
2214

tester.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,12 @@ func WithM(m TestingM) Opt {
147147
}
148148

149149
// WithFn uses the given function as the test function.
150-
func WithFn(fn func() error) Opt {
150+
func WithFn(fn func()) Opt {
151151
return func(t *Tester) error {
152-
return t.setTest(fn)
152+
return t.setTest(func() error {
153+
fn()
154+
return nil
155+
})
153156
}
154157
}
155158

tester_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ func TestRun_Errors(t *testing.T) {
2121
{
2222
name: "overwriting test function",
2323
opts: []tstr.Opt{
24-
tstr.WithFn(func() error { return nil }),
25-
tstr.WithFn(func() error { return nil }),
24+
tstr.WithFn(func() {}),
25+
tstr.WithFn(func() {}),
2626
},
2727
expectedErr: tstr.ErrOverwritingTestFn,
2828
},

0 commit comments

Comments
 (0)