Skip to content

Commit a8f8a8c

Browse files
authored
Merge pull request #17 from launchdarkly/eb/sc-166183/more-helpers
add some more test helpers for files & JSON
2 parents c1a6d39 + d9cacf4 commit a8f8a8c

File tree

6 files changed

+242
-1
lines changed

6 files changed

+242
-1
lines changed

files.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func FilePathExists(path string) bool {
2222
// helpers.WithTempFile(func(path string) {
2323
// DoSomethingWithTempFile(path)
2424
// }) // the file is deleted at the end of this block
25-
func WithTempFile(f func(string)) {
25+
func WithTempFile(f func(filePath string)) {
2626
file, err := os.CreateTemp("", "test")
2727
if err != nil {
2828
panic(fmt.Errorf("can't create temp file: %s", err))
@@ -39,3 +39,24 @@ func WithTempFile(f func(string)) {
3939
})()
4040
f(file.Name())
4141
}
42+
43+
// WithTempFileData is identical to WithTempFile except that it prepopulates the file with the
44+
// specified data.
45+
func WithTempFileData(data []byte, f func(filePath string)) {
46+
WithTempFile(func(filePath string) {
47+
if err := os.WriteFile(filePath, data, 0600); err != nil {
48+
panic(fmt.Errorf("can't write to temp file: %s", err))
49+
}
50+
f(filePath)
51+
})
52+
}
53+
54+
// WithTempDir creates a temporary directory, calls the function with its path, then removes it.
55+
func WithTempDir(f func(path string)) {
56+
path, err := os.MkdirTemp("", "test")
57+
if err != nil {
58+
panic(err)
59+
}
60+
defer os.RemoveAll(path) //nolint:errcheck
61+
f(path)
62+
}

files_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package helpers
22

33
import (
4+
"os"
5+
"path/filepath"
46
"testing"
57

68
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
710
)
811

912
func TestWithTempFile(t *testing.T) {
@@ -14,3 +17,26 @@ func TestWithTempFile(t *testing.T) {
1417
})
1518
assert.False(t, FilePathExists(filePath))
1619
}
20+
21+
func TestWithTempFileData(t *testing.T) {
22+
var filePath string
23+
WithTempFileData([]byte(`hello`), func(path string) {
24+
filePath = path
25+
data, err := os.ReadFile(path)
26+
require.NoError(t, err)
27+
assert.Equal(t, "hello", string(data))
28+
})
29+
assert.False(t, FilePathExists(filePath))
30+
}
31+
32+
func TestWithTempDir(t *testing.T) {
33+
var path string
34+
WithTempDir(func(dirPath string) {
35+
path = dirPath
36+
info, err := os.Stat(path)
37+
require.NoError(t, err)
38+
assert.True(t, info.IsDir())
39+
assert.NoError(t, os.WriteFile(filepath.Join(dirPath, "x"), []byte("hello"), 0600))
40+
})
41+
assert.False(t, FilePathExists(path))
42+
}

jsonhelpers/assertions.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package jsonhelpers
2+
3+
import (
4+
"reflect"
5+
"strings"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
// AssertEqual compares two JSON Value instances and returns true if they are deeply equal.
11+
// If they are not equal, it outputs a test failure message describing the mismatch as
12+
// specifically as possible.
13+
//
14+
// The two values may either be pre-parsed JValue instances, or if they are not, they are
15+
// converted using the same rules as JValueOf.
16+
func AssertEqual(t assert.TestingT, expected, actual any) bool {
17+
ev, av := JValueOf(expected), JValueOf(actual)
18+
if ev.err != nil {
19+
t.Errorf("invalid expected value (%s): %s", ev.err, ev)
20+
return false
21+
}
22+
if av.err != nil {
23+
t.Errorf("invalid actual value (%s): %s", av.err, av)
24+
return false
25+
}
26+
if reflect.DeepEqual(ev.parsed, av.parsed) {
27+
return true
28+
}
29+
diff := describeValueDifference(ev.parsed, av.parsed, nil)
30+
if len(diff) == 1 && diff[0].Path == nil {
31+
t.Errorf("expected JSON value: %s\nactual value: %s", expected, actual)
32+
} else {
33+
t.Errorf("incorrect JSON value: %s\n"+strings.Join(diff.Describe("expected", "actual"), "\n"), actual)
34+
}
35+
return false
36+
}

jsonhelpers/assertions_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package jsonhelpers
2+
3+
import (
4+
"testing"
5+
6+
"github.com/launchdarkly/go-test-helpers/v2/testbox"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestAssertEqual(t *testing.T) {
11+
AssertEqual(t, `{"a":true,"b":false}`, `{"b":false,"a":true}`)
12+
13+
AssertEqual(t, JValueOf(`{"a":true,"b":false}`), JValueOf(`{"b":false,"a":true}`))
14+
15+
result := testbox.SandboxTest(func(t testbox.TestingT) {
16+
AssertEqual(t, `{"a":true,"b":false}`, `{"a":false,"b":false}`)
17+
})
18+
assert.True(t, result.Failed)
19+
if assert.Len(t, result.Failures, 1) {
20+
assert.Equal(t, `incorrect JSON value: {"a":false,"b":false}
21+
at "a": expected = true, actual = false`, result.Failures[0].Message)
22+
}
23+
24+
result = testbox.SandboxTest(func(t testbox.TestingT) {
25+
AssertEqual(t, `{"a":true,"b":false}`, `{`)
26+
})
27+
assert.True(t, result.Failed)
28+
if assert.Len(t, result.Failures, 1) {
29+
assert.Equal(t, `invalid actual value (JSON unmarshaling error: unexpected end of JSON input): {`,
30+
result.Failures[0].Message)
31+
}
32+
33+
result = testbox.SandboxTest(func(t testbox.TestingT) {
34+
AssertEqual(t, `{`, `{"a":true,"b":false}`)
35+
})
36+
assert.True(t, result.Failed)
37+
if assert.Len(t, result.Failures, 1) {
38+
assert.Equal(t, `invalid expected value (JSON unmarshaling error: unexpected end of JSON input): {`,
39+
result.Failures[0].Message)
40+
}
41+
}

jsonhelpers/value.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package jsonhelpers
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"reflect"
7+
)
8+
9+
// JValue is a helper type for manipulating JSON data in tests. It validates that marshaled
10+
// data is valid JSON, allows other data to be converted to JSON, and eliminates ambiguity
11+
// as to whether a type like string or []byte in a test represents JSON or not.
12+
type JValue struct {
13+
raw string
14+
parsed any
15+
err error
16+
}
17+
18+
// String returns the JSON value as a string.
19+
func (v JValue) String() string {
20+
return v.raw
21+
}
22+
23+
// Error returns nil if the value is valid JSON, or else an error value describing the problem.
24+
func (v JValue) Error() error {
25+
return v.err
26+
}
27+
28+
// Equal returns true if the values are deeply equal.
29+
func (v JValue) Equal(v1 JValue) bool {
30+
if v.err != nil || v1.err != nil {
31+
return v.err == v1.err
32+
}
33+
return reflect.DeepEqual(v.parsed, v1.parsed)
34+
}
35+
36+
// JValueOf creates a JValue based on any input type, as follows:
37+
//
38+
// - If the input type is []byte, json.RawMessage, or string, it interprets the value as JSON.
39+
// - If the input type is JValue, it returns the same value.
40+
// - For any other type, it attempts to marshal the value to JSON.
41+
//
42+
// If the input value is invalid, the returned JValue will have a non-nil Error().
43+
func JValueOf(value any) JValue {
44+
var data []byte
45+
switch v := value.(type) {
46+
case JValue:
47+
return v
48+
case json.RawMessage:
49+
data = v
50+
case []byte:
51+
data = v
52+
case string:
53+
data = []byte(v)
54+
default:
55+
d, err := json.Marshal(value)
56+
if err != nil {
57+
return JValue{
58+
raw: "<invalid>",
59+
parsed: value,
60+
err: fmt.Errorf("value could not be marshalled to JSON: %s", err),
61+
}
62+
}
63+
data = d
64+
}
65+
var intf interface{}
66+
if err := json.Unmarshal(data, &intf); err != nil {
67+
return JValue{
68+
raw: string(data),
69+
parsed: nil,
70+
err: fmt.Errorf("JSON unmarshaling error: %s", err),
71+
}
72+
}
73+
return JValue{raw: string(data), parsed: intf, err: nil}
74+
}

jsonhelpers/value_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package jsonhelpers
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestJValueOf(t *testing.T) {
11+
s := `{"a":true}`
12+
m := map[string]interface{}{"a": true}
13+
14+
v1 := JValueOf([]byte(s))
15+
assert.Nil(t, v1.Error())
16+
assert.Equal(t, m, v1.parsed)
17+
assert.Equal(t, s, v1.String())
18+
19+
v2 := JValueOf(json.RawMessage(s))
20+
assert.Nil(t, v2.Error())
21+
assert.Equal(t, m, v2.parsed)
22+
assert.Equal(t, s, v2.String())
23+
assert.Equal(t, v1, v2)
24+
25+
v3 := JValueOf(s)
26+
assert.Nil(t, v3.Error())
27+
assert.Equal(t, m, v3.parsed)
28+
assert.Equal(t, s, v3.String())
29+
assert.Equal(t, v1, v3)
30+
31+
v4 := JValueOf(m)
32+
assert.Nil(t, v4.Error())
33+
assert.Equal(t, m, v4.parsed)
34+
assert.Equal(t, s, v4.String())
35+
assert.Equal(t, v1, v4)
36+
37+
v5 := JValueOf(v4)
38+
assert.Equal(t, v4, v5)
39+
40+
v6 := JValueOf("{no")
41+
assert.NotNil(t, v6.Error())
42+
assert.Equal(t, "{no", v6.String())
43+
}

0 commit comments

Comments
 (0)