Skip to content

Commit 6d30b8b

Browse files
MKrupauskasjeffbean
authored andcommitted
add initial assertion library
1 parent 27bc0d6 commit 6d30b8b

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

internal/assert/assert.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Internal assertion library inspired by https://github.com/stretchr/testify.
2+
// "A little copying is better than a little dependency." - https://go-proverbs.github.io.
3+
// We don't want the library to have dependencies, so we write our own assertions.
4+
package assert
5+
6+
import (
7+
"fmt"
8+
"reflect"
9+
)
10+
11+
// TestingT is an interface wrapper around stdlib *testing.T.
12+
type TestingT interface {
13+
Errorf(format string, args ...interface{})
14+
Helper()
15+
}
16+
17+
// Equal asserts that expected is equal to actual.
18+
func Equal(t TestingT, want, got interface{}, msgAndArgs ...interface{}) {
19+
if !reflect.DeepEqual(want, got) {
20+
fail(t, fmt.Sprintf("not equal: want: %+v, got: %+v", want, got), msgAndArgs)
21+
}
22+
}
23+
24+
// NoError asserts that the error is nil.
25+
func NoError(t TestingT, err error, msgAndArgs ...interface{}) {
26+
if err != nil {
27+
fail(t, fmt.Sprintf("unexpected error: %v", err), msgAndArgs)
28+
}
29+
}
30+
31+
func fail(t TestingT, message string, msgAndArgs []interface{}) {
32+
t.Helper()
33+
userMessage := msgAndArgsToString(msgAndArgs)
34+
if userMessage != "" {
35+
message += ": " + userMessage
36+
}
37+
t.Errorf(message)
38+
}
39+
40+
func msgAndArgsToString(msgAndArgs []interface{}) string {
41+
if len(msgAndArgs) == 0 {
42+
return ""
43+
}
44+
if len(msgAndArgs) == 1 {
45+
return fmt.Sprintf("%+v", msgAndArgs[0])
46+
}
47+
if format, ok := msgAndArgs[0].(string); ok {
48+
return fmt.Sprintf(format, msgAndArgs[1:]...)
49+
}
50+
return fmt.Sprintf("%+v", msgAndArgs)
51+
}

internal/assert/assert_test.go

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package assert
2+
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
type call struct {
9+
name string
10+
args []interface{}
11+
}
12+
13+
// fakeT is a fake implementation of TestingT.
14+
// It records calls to its methods.
15+
// Its methods are not safe for concurrent use.
16+
type fakeT struct {
17+
calls []call
18+
}
19+
20+
func (f *fakeT) Errorf(format string, args ...interface{}) {
21+
f.calls = append(f.calls, call{
22+
name: "Errorf",
23+
args: append([]interface{}{format}, args...),
24+
})
25+
}
26+
27+
func (f *fakeT) Helper() {
28+
f.calls = append(f.calls, call{name: "Helper"})
29+
}
30+
31+
func TestEqual(t *testing.T) {
32+
tests := []struct {
33+
name string
34+
giveWant interface{}
35+
giveGot interface{}
36+
giveMsgAndArgs []interface{}
37+
want []call
38+
}{
39+
{
40+
name: "equal",
41+
giveWant: 1,
42+
giveGot: 1,
43+
want: nil,
44+
},
45+
{
46+
name: "not equal shallow",
47+
giveWant: 1,
48+
giveGot: 2,
49+
want: []call{
50+
{name: "Helper"},
51+
{name: "Errorf", args: []interface{}{"not equal: want: 1, got: 2"}},
52+
},
53+
},
54+
{
55+
name: "not equal deep",
56+
giveWant: map[string]interface{}{"foo": struct{ bar string }{"baz"}},
57+
giveGot: map[string]interface{}{"foo": struct{ bar string }{"foobar"}},
58+
want: []call{
59+
{name: "Helper"},
60+
{name: "Errorf", args: []interface{}{"not equal: want: map[foo:{bar:baz}], got: map[foo:{bar:foobar}]"}},
61+
},
62+
},
63+
{
64+
name: "with message",
65+
giveWant: 1,
66+
giveGot: 2,
67+
giveMsgAndArgs: []interface{}{"user message"},
68+
want: []call{
69+
{name: "Helper"},
70+
{name: "Errorf", args: []interface{}{"not equal: want: 1, got: 2: user message"}},
71+
},
72+
},
73+
{
74+
name: "with message and args",
75+
giveWant: 1,
76+
giveGot: 2,
77+
giveMsgAndArgs: []interface{}{"user message: %d %s", 1, "arg2"},
78+
want: []call{
79+
{name: "Helper"},
80+
{name: "Errorf", args: []interface{}{"not equal: want: 1, got: 2: user message: 1 arg2"}},
81+
},
82+
},
83+
{
84+
name: "only args",
85+
giveWant: 1,
86+
giveGot: 2,
87+
giveMsgAndArgs: []interface{}{1, "arg2"},
88+
want: []call{
89+
{name: "Helper"},
90+
{name: "Errorf", args: []interface{}{"not equal: want: 1, got: 2: [1 arg2]"}},
91+
},
92+
},
93+
}
94+
for _, tt := range tests {
95+
t.Run(tt.name, func(t *testing.T) {
96+
var f fakeT
97+
Equal(&f, tt.giveWant, tt.giveGot, tt.giveMsgAndArgs...)
98+
// Since we're asserting ourselves it might be possible to introduce a subtle bug.
99+
// However, the code is straightforward so it's not a big deal.
100+
Equal(t, tt.want, f.calls)
101+
})
102+
}
103+
}
104+
105+
func TestNoError(t *testing.T) {
106+
tests := []struct {
107+
name string
108+
giveErr error
109+
giveMsgAndArgs []interface{}
110+
want []call
111+
}{
112+
{
113+
name: "no error",
114+
giveErr: nil,
115+
want: nil,
116+
},
117+
{
118+
name: "with error",
119+
giveErr: errors.New("foo"),
120+
want: []call{
121+
{name: "Helper"},
122+
{name: "Errorf", args: []interface{}{"unexpected error: foo"}},
123+
},
124+
},
125+
{
126+
name: "with message",
127+
giveErr: errors.New("foo"),
128+
giveMsgAndArgs: []interface{}{"user message"},
129+
want: []call{
130+
{name: "Helper"},
131+
{name: "Errorf", args: []interface{}{"unexpected error: foo: user message"}},
132+
},
133+
},
134+
{
135+
name: "with message and args",
136+
giveErr: errors.New("foo"),
137+
giveMsgAndArgs: []interface{}{"user message: %d %s", 1, "arg2"},
138+
want: []call{
139+
{name: "Helper"},
140+
{name: "Errorf", args: []interface{}{"unexpected error: foo: user message: 1 arg2"}},
141+
},
142+
},
143+
{
144+
name: "only args",
145+
giveErr: errors.New("foo"),
146+
giveMsgAndArgs: []interface{}{1, "arg2"},
147+
want: []call{
148+
{name: "Helper"},
149+
{name: "Errorf", args: []interface{}{"unexpected error: foo: [1 arg2]"}},
150+
},
151+
},
152+
}
153+
for _, tt := range tests {
154+
t.Run(tt.name, func(t *testing.T) {
155+
var f fakeT
156+
NoError(&f, tt.giveErr, tt.giveMsgAndArgs...)
157+
Equal(t, tt.want, f.calls)
158+
})
159+
}
160+
}

0 commit comments

Comments
 (0)