Skip to content

Commit 1da9ddb

Browse files
authored
Merge pull request #1 from perrito666/horacio/jsondiff
2 parents 635a098 + feafe8e commit 1da9ddb

File tree

6 files changed

+350
-116
lines changed

6 files changed

+350
-116
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ func TestMain(m *testing.M) {
5151

5252
Once included, if the update `-u` flag is used when running tests, any snapshot that is no longer in use will be removed. Note: if a single test is run, pruning _will not occur_.
5353

54+
Alternatively `CleanupOrFail` can be used to fail a test run if a snapshot needs cleaning up but the `-u` flag wasn't given (and it's not a single-test run):
55+
56+
```go
57+
func TestMain(m *testing.M) {
58+
if m.Run() == 0 {
59+
if err := abide.CleanupOrFail(); err != nil {
60+
fmt.Fprintln(os.Stderr, err.Error())
61+
os.Exit(1)
62+
}
63+
}
64+
}
65+
```
66+
5467
## Snapshots
5568

5669
A snapshot is essentially a lock file for an http response. Instead of having to manually compare every aspect of an http response to it's expected value, it can be automatically generated and used for matching in subsequent testing.
@@ -105,4 +118,4 @@ To write snapshots to a directory other than the default `__snapshot__`, adjust
105118
func init() {
106119
abide.SnapshotDir = "testdata"
107120
}
108-
```
121+
```

abide.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ const (
3232
snapshotSeparator = "/* snapshot: "
3333
)
3434

35+
type SnapshotType string
36+
37+
const (
38+
// SnapshotGeneric represents a snapshot whose contents we assume have no known format.
39+
SnapshotGeneric SnapshotType = ""
40+
// SnapshotHTTPRespJSON represents a snapshot whose contents are an HTTP response with content type JSON.
41+
SnapshotHTTPRespJSON SnapshotType = "HTTPContentTypeJSON"
42+
)
43+
3544
func init() {
3645
// Get arguments
3746
args = getArguments()
@@ -50,6 +59,32 @@ func Cleanup() error {
5059
return allSnapshots.save()
5160
}
5261

62+
// CleanupOrFail is an optional method which will behave like
63+
// Cleanup() if the `-u` flag was given, but which returns an error if
64+
// `-u` was not given and there were things to clean up.
65+
func CleanupOrFail() error {
66+
if args.singleRun {
67+
return nil
68+
}
69+
if args.shouldUpdate {
70+
return Cleanup()
71+
}
72+
73+
failed := 0
74+
for _, s := range allSnapshots {
75+
if !s.evaluated {
76+
failed++
77+
fmt.Fprintf(os.Stderr, "Unused snapshot `%s`\n", s.id)
78+
}
79+
}
80+
81+
if failed > 0 {
82+
return fmt.Errorf("%d unused snapshots", failed)
83+
}
84+
85+
return nil
86+
}
87+
5388
// snapshotID represents the unique identifier for a snapshot.
5489
type snapshotID string
5590

abide_test.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package abide
22

33
import (
4+
"fmt"
45
"os"
56
"reflect"
67
"testing"
@@ -22,7 +23,7 @@ func testingSnapshot(id, value string) *snapshot {
2223
func testingSnapshots(count int) snapshots {
2324
s := make(snapshots, count)
2425
for i := 0; i < count; i++ {
25-
id := string(i)
26+
id := string(rune(i))
2627
s[snapshotID(id)] = testingSnapshot(id, id)
2728
}
2829
return s
@@ -34,6 +35,8 @@ func TestCleanup(t *testing.T) {
3435
_ = testingSnapshot("1", "A")
3536

3637
// If shouldUpdate = false, the snapshot must remain.
38+
args.shouldUpdate = false
39+
args.singleRun = false
3740
err := Cleanup()
3841
if err != nil {
3942
t.Fatal(err)
@@ -69,13 +72,63 @@ func TestCleanup(t *testing.T) {
6972
}
7073
}
7174

75+
func TestCleanupOrFail(t *testing.T) {
76+
defer testingCleanup()
77+
78+
_ = testingSnapshot("1", "A")
79+
80+
args.shouldUpdate = false
81+
args.singleRun = true
82+
// singleRun means no cleanup
83+
err := CleanupOrFail()
84+
if err != nil {
85+
t.Fatal(err)
86+
}
87+
88+
// shouldUpdate=false and singleRun=false -> CleanupOrFail fails
89+
args.singleRun = false
90+
err = CleanupOrFail()
91+
if fmt.Sprint(err) != "1 unused snapshots" {
92+
t.Fatalf("expected `1 unused snapshots`, got %v", err)
93+
}
94+
95+
err = loadSnapshots()
96+
if err != nil {
97+
t.Fatal(err)
98+
}
99+
100+
snapshot := getSnapshot("1")
101+
if snapshot == nil {
102+
t.Fatal("Expected snapshot[1] to exist.")
103+
}
104+
105+
// If shouldUpdate = true and singleRun = false, the snapshot must be removed.
106+
args.shouldUpdate = true
107+
args.singleRun = false
108+
err = CleanupOrFail()
109+
if err != nil {
110+
t.Fatal(err)
111+
}
112+
113+
// call private reloadSnapshots to repeat once-executing function
114+
err = reloadSnapshots()
115+
if err != nil {
116+
t.Fatal(err)
117+
}
118+
119+
snapshot = getSnapshot("1")
120+
if snapshot != nil {
121+
t.Fatal("Expected snapshot[1] to be removed.")
122+
}
123+
}
124+
72125
func TestCleanupUpdate(t *testing.T) {
73126
defer testingCleanup()
74127

75128
// this snapshot is updated, should be evaluated, and not removed
76129
_ = testingSnapshot("1", "A")
77130
t2 := &testing.T{}
78-
createOrUpdateSnapshot(t2, "1", "B")
131+
createOrUpdateSnapshot(t2, "1", "B", SnapshotGeneric)
79132

80133
// this snapshot is never evaluated, and should be removed
81134
_ = testingSnapshot("2", "B")

assert.go

Lines changed: 12 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
package abide
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"io"
76
"io/ioutil"
8-
"net/http"
9-
"net/http/httputil"
107
"strings"
118
"testing"
129

13-
"github.com/beme/abide/internal"
1410
"github.com/sergi/go-diff/diffmatchpatch"
1511
)
1612

@@ -22,108 +18,7 @@ type Assertable interface {
2218
// Assert asserts the value of an object with implements Assertable.
2319
func Assert(t *testing.T, id string, a Assertable) {
2420
data := a.String()
25-
createOrUpdateSnapshot(t, id, data)
26-
}
27-
28-
// AssertHTTPResponse asserts the value of an http.Response.
29-
func AssertHTTPResponse(t *testing.T, id string, w *http.Response) {
30-
body, err := httputil.DumpResponse(w, true)
31-
if err != nil {
32-
t.Fatal(err)
33-
}
34-
35-
assertHTTP(t, id, body, contentTypeIsJSON(w.Header.Get("Content-Type")))
36-
}
37-
38-
// AssertHTTPRequestOut asserts the value of an http.Request.
39-
// Intended for use when testing outgoing client requests
40-
// See https://golang.org/pkg/net/http/httputil/#DumpRequestOut for more
41-
func AssertHTTPRequestOut(t *testing.T, id string, r *http.Request) {
42-
body, err := httputil.DumpRequestOut(r, true)
43-
if err != nil {
44-
t.Fatal(err)
45-
}
46-
47-
assertHTTP(t, id, body, contentTypeIsJSON(r.Header.Get("Content-Type")))
48-
}
49-
50-
// AssertHTTPRequest asserts the value of an http.Request.
51-
// Intended for use when testing incoming client requests
52-
// See https://golang.org/pkg/net/http/httputil/#DumpRequest for more
53-
func AssertHTTPRequest(t *testing.T, id string, r *http.Request) {
54-
body, err := httputil.DumpRequest(r, true)
55-
if err != nil {
56-
t.Fatal(err)
57-
}
58-
59-
assertHTTP(t, id, body, contentTypeIsJSON(r.Header.Get("Content-Type")))
60-
}
61-
62-
func assertHTTP(t *testing.T, id string, body []byte, isJSON bool) {
63-
config, err := getConfig()
64-
if err != nil {
65-
t.Fatal(err)
66-
}
67-
68-
data := string(body)
69-
lines := strings.Split(strings.TrimSpace(data), "\n")
70-
71-
if config != nil {
72-
// empty line identifies the end of the HTTP header
73-
for i, line := range lines {
74-
if line == "" {
75-
break
76-
}
77-
78-
headerItem := strings.Split(line, ":")
79-
if def, ok := config.Defaults[headerItem[0]]; ok {
80-
lines[i] = fmt.Sprintf("%s: %s", headerItem[0], def)
81-
}
82-
}
83-
}
84-
85-
// If the response body is JSON, indent.
86-
if isJSON {
87-
jsonStr := lines[len(lines)-1]
88-
89-
var jsonIface map[string]interface{}
90-
err = json.Unmarshal([]byte(jsonStr), &jsonIface)
91-
if err != nil {
92-
t.Fatal(err)
93-
}
94-
95-
// Clean/update json based on config.
96-
if config != nil {
97-
for k, v := range config.Defaults {
98-
jsonIface = internal.UpdateKeyValuesInMap(k, v, jsonIface)
99-
}
100-
}
101-
102-
out, err := json.MarshalIndent(jsonIface, "", " ")
103-
if err != nil {
104-
t.Fatal(err)
105-
}
106-
lines[len(lines)-1] = string(out)
107-
}
108-
109-
data = strings.Join(lines, "\n")
110-
createOrUpdateSnapshot(t, id, data)
111-
}
112-
113-
func contentTypeIsJSON(contentType string) bool {
114-
contentTypeParts := strings.Split(contentType, ";")
115-
firstPart := contentTypeParts[0]
116-
117-
isPlainJSON := firstPart == "application/json"
118-
if isPlainJSON {
119-
return isPlainJSON
120-
}
121-
122-
isVendor := strings.HasPrefix(firstPart, "application/vnd.")
123-
124-
isJSON := strings.HasSuffix(firstPart, "+json")
125-
126-
return isVendor && isJSON
21+
createOrUpdateSnapshot(t, id, data, SnapshotGeneric)
12722
}
12823

12924
// AssertReader asserts the value of an io.Reader.
@@ -133,10 +28,10 @@ func AssertReader(t *testing.T, id string, r io.Reader) {
13328
t.Fatal(err)
13429
}
13530

136-
createOrUpdateSnapshot(t, id, string(data))
31+
createOrUpdateSnapshot(t, id, string(data), SnapshotGeneric)
13732
}
13833

139-
func createOrUpdateSnapshot(t *testing.T, id, data string) {
34+
func createOrUpdateSnapshot(t *testing.T, id, data string, format SnapshotType) {
14035
var err error
14136
snapshot := getSnapshot(snapshotID(id))
14237

@@ -156,7 +51,14 @@ func createOrUpdateSnapshot(t *testing.T, id, data string) {
15651
}
15752

15853
snapshot.evaluated = true
159-
diff := compareResults(t, snapshot.value, strings.TrimSpace(data))
54+
var diff string
55+
switch format {
56+
case SnapshotHTTPRespJSON:
57+
diff = compareResultsHTTPRequestJSON(t, snapshot.value, strings.TrimSpace(data))
58+
default:
59+
diff = compareResults(t, snapshot.value, strings.TrimSpace(data))
60+
}
61+
16062
if diff != "" {
16163
if snapshot != nil && args.shouldUpdate {
16264
fmt.Printf("Updating snapshot `%s`\n", id)
@@ -176,7 +78,7 @@ func compareResults(t *testing.T, existing, new string) string {
17678
dmp := diffmatchpatch.New()
17779
dmp.PatchMargin = 20
17880
allDiffs := dmp.DiffMain(existing, new, false)
179-
nonEqualDiffs := []diffmatchpatch.Diff{}
81+
var nonEqualDiffs []diffmatchpatch.Diff
18082
for _, diff := range allDiffs {
18183
if diff.Type != diffmatchpatch.DiffEqual {
18284
nonEqualDiffs = append(nonEqualDiffs, diff)

0 commit comments

Comments
 (0)