Skip to content

Commit acf0980

Browse files
authored
Merge pull request #3 from go-tstr/allow-configuration
Allow configuring golden file behavior
2 parents 0f5b1ec + 960bba0 commit acf0980

File tree

2 files changed

+103
-47
lines changed

2 files changed

+103
-47
lines changed

file.go

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,26 @@ import (
77
"io"
88
"net/http"
99
"os"
10+
"path/filepath"
1011
"strconv"
1112
"strings"
1213

1314
"github.com/stretchr/testify/assert"
14-
"github.com/stretchr/testify/require"
1515
)
1616

17-
var overwrite, _ = strconv.ParseBool(os.Getenv("OVERWRITE_GOLDEN_FILES"))
17+
var DefaultHandler = &FileHandler{
18+
FileName: TestNameToFilePath,
19+
ShouldRecreate: ParseRecreateFromEnv,
20+
Equal: EqualWithDiff,
21+
ProcessContent: nil,
22+
}
23+
24+
type FileHandler struct {
25+
FileName func(T) string
26+
ShouldRecreate func(T) bool
27+
ProcessContent func(T, string) string
28+
Equal func(t T, expected, actual string, msgAndArgs ...interface{}) (ok bool)
29+
}
1830

1931
type T interface {
2032
Logf(format string, args ...any)
@@ -24,6 +36,7 @@ type T interface {
2436
Helper()
2537
}
2638

39+
// Client is an interface that allows using http.Client or any other client that implements the Do method.
2740
type Client interface {
2841
Do(req *http.Request) (*http.Response, error)
2942
}
@@ -58,35 +71,56 @@ type Client interface {
5871
// }
5972
// }
6073
func Request(t T, client Client, req *http.Request, expectedStatusCode int) (*http.Response, bool) {
61-
t.Helper()
74+
return DefaultHandler.Request(t, client, req, expectedStatusCode)
75+
}
76+
77+
// Assert checks the golden file content against the given data.
78+
func Assert(t T, data string) bool {
79+
return DefaultHandler.Assert(t, data)
80+
}
81+
82+
func (h *FileHandler) Request(t T, client Client, req *http.Request, expectedStatusCode int) (*http.Response, bool) {
6283
resp, err := client.Do(req)
63-
require.NoError(t, err, "client.Do failed")
84+
NoError(t, err, "client.Do failed")
85+
86+
ok := true
87+
if resp.StatusCode != expectedStatusCode {
88+
ok = false
89+
t.Errorf("expected status code %d, got %d", expectedStatusCode, resp.StatusCode)
90+
}
6491

65-
ok := assert.Equal(t, expectedStatusCode, resp.StatusCode, "unexpected status code")
6692
body, err := io.ReadAll(resp.Body)
67-
ok = assert.NoError(t, err, "reading response body failed") && ok
93+
NoError(t, err, "reading response body failed")
94+
6895
resp.Body = io.NopCloser(bytes.NewReader(body))
69-
return resp, Equal(t, string(body)) && ok
96+
return resp, h.Assert(t, string(body)) && ok
7097
}
7198

72-
// Equal asserts that the golden file content is equal to the data in string format.
73-
func Equal(t T, data string) bool {
99+
func (h *FileHandler) Assert(t T, data string) bool {
74100
t.Helper()
75-
return assert.Equal(t, File(t, data), string(data))
101+
if h.ProcessContent != nil {
102+
data = h.ProcessContent(t, data)
103+
}
104+
return h.Equal(t, h.loadAndSaveFile(t, data), data)
76105
}
77106

78-
// File returns the golden file content for the test.
79-
// If OVERWRITE_GOLDEN_FILES env is set to true, the golden file will be created with the content of the data.
80-
// OVERWRITE_GOLDEN_FILES is read only once at the start of the test and it's value is not updated.
81-
// Depending of the test structure the golden file and it's directories arew created in
82-
// ./testdata/{testFuncName}/{subTestName}.golden or ./testdata/{testFuncName}/{testFuncName}.golden.
83-
func File(t T, data string) string {
84-
t.Helper()
85-
return file(t, data, overwrite)
107+
func (h *FileHandler) loadAndSaveFile(t T, data string) string {
108+
fileName := h.FileName(t)
109+
if h.ShouldRecreate(t) {
110+
t.Logf("recreating golden file: %s", fileName)
111+
NoError(t, os.MkdirAll(filepath.Dir(fileName), 0o755), "failed to create testdata directory for golden file")
112+
NoError(t, os.WriteFile(fileName, []byte(data), 0o600), "failed to write golden file")
113+
}
114+
115+
b, err := os.ReadFile(fileName)
116+
NoError(t, err, "failed to read golden file")
117+
return string(b)
86118
}
87119

88-
func file(t T, data string, recreate bool) string {
89-
t.Helper()
120+
// TestNameToFilePath creates file name and path for the golden file using t.Name() with following rules:
121+
// Top level: ./testdata/{testFuncName}/{testFuncName}.golden
122+
// Subtest: ./testdata/{testFuncName}/{subTestName}.golden
123+
func TestNameToFilePath(t T) string {
90124
split := strings.SplitN(t.Name(), "/", 2)
91125
mainTestName := t.Name()
92126
testName := t.Name()
@@ -95,15 +129,29 @@ func file(t T, data string, recreate bool) string {
95129
testName = strings.ReplaceAll(split[1], "/", "_")
96130
}
97131

98-
folderName := fmt.Sprintf("./testdata/%s", mainTestName)
99-
fileName := strings.ReplaceAll(fmt.Sprintf("%s/%s.golden", folderName, testName), " ", "_")
100-
if recreate {
101-
t.Logf("recreating golden file: %s", fileName)
102-
require.NoError(t, os.MkdirAll(folderName, 0o755), "failed to create testdata directory for golden file")
103-
require.NoError(t, os.WriteFile(fileName, []byte(data), 0o600), "failed to write golden file")
132+
return strings.ReplaceAll(filepath.Join("./testdata/", mainTestName, testName+".golden"), " ", "_")
133+
}
134+
135+
// ParseRecreateFromEnv checks if the environment variable GOLDEN_FILES_RECREATE is set to true.
136+
func ParseRecreateFromEnv(t T) bool {
137+
str := os.Getenv("GOLDEN_FILES_RECREATE")
138+
if str == "" {
139+
return false
104140
}
105141

106-
b, err := os.ReadFile(fileName)
107-
require.NoError(t, err, "failed to read golden file")
108-
return string(b)
142+
overwrite, err := strconv.ParseBool(str)
143+
NoError(t, err, fmt.Sprintf("failed to parse GOLDEN_FILES_RECREATE env variable: '%s' to bool", str))
144+
return overwrite
145+
}
146+
147+
func NoError(t T, err error, msg string) {
148+
t.Helper()
149+
if err != nil {
150+
t.Errorf("%s: %s", msg, err)
151+
t.FailNow()
152+
}
153+
}
154+
155+
func EqualWithDiff(t T, expected, actual string, msgAndArgs ...interface{}) (ok bool) {
156+
return assert.Equal(t, expected, actual, msgAndArgs...)
109157
}

file_test.go

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package golden
1+
package golden_test
22

33
import (
44
"fmt"
@@ -7,6 +7,7 @@ import (
77
"os"
88
"testing"
99

10+
"github.com/go-tstr/golden"
1011
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
1213
)
@@ -52,12 +53,20 @@ func TestFile(t *testing.T) {
5253
},
5354
}
5455

56+
fh := &golden.FileHandler{
57+
FileName: golden.TestNameToFilePath,
58+
ShouldRecreate: golden.ParseRecreateFromEnv,
59+
Equal: golden.EqualWithDiff,
60+
ProcessContent: nil,
61+
}
62+
5563
t.Run("create", func(t *testing.T) {
64+
t.Setenv("GOLDEN_FILES_RECREATE", "true")
5665
for _, tt := range tests {
5766
t.Run(tt.name, func(t *testing.T) {
5867
mt := &tt.mt
59-
got := file(mt, tt.data, true)
60-
assertResult(t, tt, mt, got)
68+
fh.Assert(mt, tt.data)
69+
assertResult(t, tt, mt)
6170
})
6271
}
6372
})
@@ -66,22 +75,23 @@ func TestFile(t *testing.T) {
6675
for _, tt := range tests {
6776
t.Run(tt.name, func(t *testing.T) {
6877
mt := &tt.mt
69-
got := File(mt, tt.data)
70-
assertResult(t, tt, mt, got)
78+
fh.Assert(mt, tt.data)
79+
assertResult(t, tt, mt)
7180
})
7281
}
7382
})
7483

7584
const suffix = " overwrite"
7685

7786
t.Run("overwrite", func(t *testing.T) {
87+
t.Setenv("GOLDEN_FILES_RECREATE", "true")
7888
for _, tt := range tests {
7989
tt.data += suffix
8090
tt.expectedData += suffix
8191
t.Run(tt.name, func(t *testing.T) {
8292
mt := &tt.mt
83-
got := file(mt, tt.data, true)
84-
assertResult(t, tt, mt, got)
93+
fh.Assert(mt, tt.data)
94+
assertResult(t, tt, mt)
8595
})
8696
}
8797
})
@@ -92,19 +102,18 @@ func TestFile(t *testing.T) {
92102
tt.expectedData += suffix
93103
t.Run(tt.name, func(t *testing.T) {
94104
mt := &tt.mt
95-
got := File(mt, tt.data)
96-
assertResult(t, tt, mt, got)
105+
fh.Assert(mt, tt.data)
106+
assertResult(t, tt, mt)
97107
})
98108
}
99109
})
100110
}
101111

102112
func TestFolderDoesNotExist(t *testing.T) {
103113
mt := mockT{name: "TestDirFail"}
104-
got := File(&mt, "data")
105-
assert.Empty(t, got)
114+
golden.Assert(&mt, "data")
106115
assert.True(t, mt.failed)
107-
assert.Contains(t, mt.msg, "open ./testdata/TestDirFail/TestDirFail.golden: no such file or directory")
116+
assert.Contains(t, mt.msg, "open testdata/TestDirFail/TestDirFail.golden: no such file or directory")
108117
assert.NoDirExists(t, "./testdata/TestDirFail")
109118
}
110119

@@ -116,7 +125,7 @@ func TestEqual(t *testing.T) {
116125
assert.NoError(t, os.WriteFile("./testdata/TestSomeString/TestSomeString.golden", []byte(data), 0o600))
117126

118127
mt := mockT{name: "TestSomeString"}
119-
got := Equal(&mt, data)
128+
got := golden.Assert(&mt, data)
120129
assert.True(t, got)
121130
assert.Empty(t, mt.msg)
122131
assert.False(t, mt.failed)
@@ -130,7 +139,7 @@ func TestEqual_No_Match(t *testing.T) {
130139
assert.NoError(t, os.WriteFile("./testdata/TestSomeOtherString/TestSomeOtherString.golden", data, 0o600))
131140

132141
mt := mockT{name: "TestSomeOtherString"}
133-
got := Equal(&mt, "other string")
142+
got := golden.Assert(&mt, "other string")
134143
assert.False(t, got)
135144
assert.Contains(t, mt.msg, "Not equal:")
136145
assert.False(t, mt.failed) // In case of assert failure, FailNow is not called
@@ -144,12 +153,11 @@ func TestRequest(t *testing.T) {
144153

145154
req, err := http.NewRequest(http.MethodGet, srv.URL, nil)
146155
require.NoError(t, err)
147-
Request(t, http.DefaultClient, req, http.StatusBadRequest)
156+
golden.Request(t, http.DefaultClient, req, http.StatusBadRequest)
148157
}
149158

150-
func assertResult(t *testing.T, tt test, mt *mockT, got string) {
159+
func assertResult(t *testing.T, tt test, mt *mockT) {
151160
t.Helper()
152-
assert.Equal(t, tt.expectedData, got)
153161
assert.Empty(t, mt.msg)
154162
assert.False(t, mt.failed)
155163
assert.FileExists(t, tt.expectedPath)
@@ -166,6 +174,6 @@ type mockT struct {
166174

167175
func (m *mockT) Name() string { return m.name }
168176
func (m *mockT) Logf(f string, args ...interface{}) { fmt.Printf(f, args...) }
169-
func (m *mockT) Errorf(f string, args ...interface{}) { m.msg = fmt.Sprintf(f, args...) }
177+
func (m *mockT) Errorf(f string, args ...interface{}) { m.msg += "\n" + fmt.Sprintf(f, args...) }
170178
func (m *mockT) FailNow() { m.failed = true }
171179
func (m *mockT) Helper() {}

0 commit comments

Comments
 (0)