Skip to content

Commit 823aebc

Browse files
committed
Add Testee.Query to access map easily
1 parent 59f270f commit 823aebc

File tree

6 files changed

+164
-0
lines changed

6 files changed

+164
-0
lines changed

all_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,13 @@ func TestRequire(t *testing.T) {
159159
// })
160160
// mint.Expect(t, ok).ToBe(false)
161161
}
162+
163+
func TestTestee_Query(t *testing.T) {
164+
v := map[string]any{
165+
"foo": map[string]any{"name": "otiai10", "age": 30},
166+
"bar": []any{100, "helllo"},
167+
}
168+
mint.Expect(t, "foo").Query("foo").ToBe("foo")
169+
mint.Expect(t, "foo").Query("bar").Not().ToBe("bar")
170+
mint.Expect(t, v).Query("foo.name").ToBe("otiai10")
171+
}

mquery/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
mquery
2+
===
3+
4+
```go
5+
import mquery
6+
7+
var m = map[string]any{
8+
"foo": "bar",
9+
"hoge": map[string]any{
10+
"name": "otiai10",
11+
},
12+
"fuga": map[int]map[string]any{
13+
0: {"greet": "Hello"},
14+
1: {"greet": "こんにちは"},
15+
},
16+
"langs": []string{"Go", "JavaScript", "English"},
17+
"baz": nil,
18+
"required": false,
19+
}
20+
21+
func main() {
22+
fmt.Println(
23+
Query(m, "foo"), // "bar"
24+
Query(m, "hoge.name"), // "otiai10"
25+
Query(m, "fuga.0.greet"), // "Hello"
26+
Query(m, "langs.2"), // "English"
27+
Query(m, "required"), // false
28+
Query(m, "baz.biz"), // nil
29+
)
30+
}
31+
```

mquery/all_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package mquery
2+
3+
import (
4+
"testing"
5+
)
6+
7+
var m = map[string]any{
8+
"foo": "bar",
9+
"hoge": map[string]any{
10+
"name": "otiai10",
11+
},
12+
"fuga": map[int]map[string]any{
13+
0: {"greet": "Hello"},
14+
1: {"greet": "こんにちは"},
15+
},
16+
"langs": []string{"Go", "JavaScript", "English"},
17+
"baz": nil,
18+
"required": false,
19+
}
20+
21+
func TestQuery(t *testing.T) {
22+
// Expect(t, Query(m, "foo")).ToBe("bar")
23+
// Expect(t, Query(m, "hoge.name")).ToBe("otiai10")
24+
// Expect(t, Query(m, "fuga.1.greet")).ToBe("こんにちは")
25+
// Expect(t, Query(m, "langs.0")).ToBe("Go")
26+
}

mquery/example_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package mquery
2+
3+
var a = map[string]any{
4+
"foo": "bar",
5+
"hoge": map[string]any{
6+
"name": "otiai10",
7+
},
8+
"fuga": map[int]map[string]any{
9+
0: {"greet": "Hello"},
10+
1: {"greet": "こんにちは"},
11+
},
12+
"langs": []string{"Go", "JavaScript", "English"},
13+
"baz": nil,
14+
"required": false,
15+
}
16+
17+
func ExampleQuery() {
18+
// Query(m, "foo") => "bar"
19+
// Query(m, "hoge.name") => "otiai10"
20+
// Query(m, "fuga.1.greet") => "こんにちは"
21+
// Query(m, "langs.0") => "Go"
22+
// Query(m, "baz") => nil
23+
// Query(m, "required") => false
24+
}

mquery/mquery.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package mquery
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
func Query(m any, q string) any {
11+
return query(m, strings.Split(q, "."))
12+
}
13+
14+
func query(m any, qs []string) any {
15+
t := reflect.TypeOf(m)
16+
switch t.Kind() {
17+
case reflect.Map:
18+
return queryMap(m, t, qs)
19+
case reflect.Slice:
20+
return querySlice(m, t, qs)
21+
default:
22+
return m
23+
}
24+
}
25+
26+
func queryMap(m any, t reflect.Type, qs []string) any {
27+
if len(qs) == 0 {
28+
return m
29+
}
30+
switch t.Key().Kind() {
31+
case reflect.String:
32+
next := reflect.ValueOf(m).MapIndex(reflect.ValueOf(qs[0])).Interface()
33+
return query(next, qs[1:])
34+
case reflect.Int:
35+
i, err := strconv.Atoi(qs[0])
36+
if err != nil {
37+
return fmt.Errorf("cannot access map with keyword: %s: %v", qs[0], err)
38+
}
39+
next := reflect.ValueOf(m).MapIndex(reflect.ValueOf(i)).Interface()
40+
return query(next, qs[1:])
41+
}
42+
return nil
43+
}
44+
45+
func querySlice(m any, t reflect.Type, qs []string) any {
46+
if len(qs) == 0 {
47+
return m
48+
}
49+
v := reflect.ValueOf(m)
50+
if v.Len() == 0 {
51+
return nil
52+
}
53+
i, err := strconv.Atoi(qs[0])
54+
if err != nil {
55+
return fmt.Errorf("cannot access slice with keyword: %s: %v", qs[0], err)
56+
}
57+
if v.Len() <= i {
58+
return nil
59+
}
60+
next := v.Index(i).Interface()
61+
return query(next, qs[1:])
62+
}

testee.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"regexp"
88
"runtime"
99
"testing"
10+
11+
"github.com/otiai10/mint/mquery"
1012
)
1113

1214
// Testee is holder of interfaces which user want to assert
@@ -21,6 +23,15 @@ type Testee struct {
2123
result MintResult
2224
required bool
2325
verbose bool
26+
27+
queriedFrom string // Only used when querying
28+
}
29+
30+
// Query queries the actual value with given query string.
31+
func (testee *Testee) Query(query string) *Testee {
32+
testee.queriedFrom = fmt.Sprintf("queried from %T", testee.actual)
33+
testee.actual = mquery.Query(testee.actual, query)
34+
return testee
2435
}
2536

2637
// ToBe can assert the testee to equal the parameter of this func.

0 commit comments

Comments
 (0)