Skip to content

Commit

Permalink
feat: implement MatchJSON snapshot function (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
gkampitakis authored Oct 30, 2022
1 parent 16a6ae1 commit 864d5ba
Show file tree
Hide file tree
Showing 22 changed files with 401 additions and 121 deletions.
74 changes: 48 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
## Highlights

- [Installation](#installation)
- [Usage](#usage)
- [MatchSnapshot](#matchsnapshot)
- [MatchJSON](#matchjson)
- [Update Snapshots](#update-snapshots)
- [Clean obsolete Snapshots](#clean-obsolete-snapshots)
- [Skipping Tests](#skipping-tests)
- [No Color](#no-color)
- [Clean obsolete Snapshots](#clean-obsolete-snapshots)
- [Snapshots Structure](#snapshots-structure)
- [Acknowledgments](#acknowledgments)
- [Contributing](./contributing.md)
Expand Down Expand Up @@ -60,7 +62,9 @@ func TestExample(t *testing.T) {
}
```

## Usage
## MatchSnapshot

`MatchSnapshot` can be used to capture any type of data structured or unstructured.

You can pass multiple parameters to `MatchSnapshot` or call `MatchSnapshot` multiple
times inside the same test. The difference is in the latter, it will
Expand All @@ -83,6 +87,29 @@ name is the test file name with extension `.snap`.
So for example if your test is called `test_simple.go` when you run your tests, a snapshot file
will be created at `./__snapshots__/test_simple.snaps`.


## MatchJSON

`MatchJSON` can be used to capture data that can represent a valid json.

You can pass a valid json in form of `string` or `[]byte` or whatever value can be passed
successfully on `json.Marshal`.

```go
func TestJSON(t *testing.T) {
type User struct {
Age int
Email string
}

snaps.MatchJSON(t, `{"user":"mock-user","age":10,"email":"mock@email.com"}`)
snaps.MatchJSON(t, []byte(`{"user":"mock-user","age":10,"email":"mock@email.com"}`))
snaps.MatchJSON(t, User{10, "mock-email"})
}
```

JSON will be saved in snapshot in pretty format for more readability.

## Update Snapshots

You can update your failing snapshots by setting `UPDATE_SNAPS` env variable to true.
Expand All @@ -99,18 +126,7 @@ For more information for `go test` flags you can run
go help testflag
```

## No Color

`go-snaps` supports disabling color outputs by running your tests with the env variable
`NO_COLOR` set to any value.

```bash
NO_COLOR=true go test ./...
```

For more information around [NO_COLOR](https://no-color.org).

## Clean obsolete snapshots
### Clean obsolete snapshots

<p align="center">
<img src="./images/summary-obsolete.png" alt="Summary Obsolete" width="700"/>
Expand Down Expand Up @@ -144,17 +160,27 @@ func TestMain(t *testing.M) {

For more information around [TestMain](https://pkg.go.dev/testing#hdr-Main).

#### Skipping Tests
### Skipping Tests

If you want to skip one test using `t.Skip`, `go-snaps` can't keep track
if the test was skipped or if it was removed. For that reason `go-snaps` exposes
a light wrapper for `t.Skip`, `t.Skipf` and `t.SkipNow` which help `go-snaps` identify
the skipped tests.
a wrapper for `t.Skip`, `t.Skipf` and `t.SkipNow`, which keep tracks of skipped files.

You can skip, or only run specific tests by using the `-run` flag. `go-snaps`
can "understand" which tests are being skipped and parse only the relevant tests
can identify which tests are being skipped and parse only the relevant tests
for obsolete snapshots.

## No Color

`go-snaps` supports disabling color outputs by running your tests with the env variable
`NO_COLOR` set to any value.

```bash
NO_COLOR=true go test ./...
```

For more information around [NO_COLOR](https://no-color.org).

## Snapshots Structure

Snapshots have the form
Expand All @@ -179,6 +205,8 @@ map[string]interface {}{
---
```

> `*.snap` files are not meant to be edited manually, this might cause unexpected results.
## Acknowledgments

This library used [Jest Snapshoting](https://jestjs.io/docs/snapshot-testing) and [Cupaloy](https://github.com/bradleyjkemp/cupaloy) as inspiration.
Expand All @@ -198,10 +226,4 @@ This library used [Jest Snapshoting](https://jestjs.io/docs/snapshot-testing) an
and after a new line, `go-snaps` will "escape" them and save them as `/-/-/-/`. This
should not cause any diff issues (false-positives).

4. Snapshots should be treated as code.

5. `.snap` files are not meant to be edited manually, this might cause unexpected results.

#### License

MIT
4. Snapshots should be treated as code. The snapshot artifact should be committed alongside code changes, and reviewed as part of your code review process
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ require (
github.com/kr/pretty v0.3.0
)

require github.com/rogpeppe/go-internal v1.9.0 // indirect
require (
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/tidwall/gjson v1.14.3
github.com/tidwall/pretty v1.2.0
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,11 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
2 changes: 1 addition & 1 deletion golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ linters-settings:

run:
skip-files:
- snaps/internal/difflib/difflib.go
- internal/difflib/difflib.go
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strings"
"testing"

"github.com/gkampitakis/go-snaps/snaps/internal/test"
"github.com/gkampitakis/go-snaps/internal/test"
)

func TestPrintColors(t *testing.T) {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strings"
"testing"

"github.com/gkampitakis/go-snaps/snaps/internal/test"
"github.com/gkampitakis/go-snaps/internal/test"
)

func TestGetOptCodes(t *testing.T) {
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion snaps/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"sync"
"testing"

"github.com/gkampitakis/go-snaps/snaps/internal/colors"
"github.com/gkampitakis/go-snaps/internal/colors"
)

// Matches [ Test... - number ] testIDs
Expand Down
2 changes: 1 addition & 1 deletion snaps/clean_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"sort"
"testing"

"github.com/gkampitakis/go-snaps/snaps/internal/test"
"github.com/gkampitakis/go-snaps/internal/test"
)

const mockSnap1 = `
Expand Down
4 changes: 2 additions & 2 deletions snaps/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"strings"

"github.com/gkampitakis/go-diff/diffmatchpatch"
"github.com/gkampitakis/go-snaps/snaps/internal/colors"
"github.com/gkampitakis/go-snaps/snaps/internal/difflib"
"github.com/gkampitakis/go-snaps/internal/colors"
"github.com/gkampitakis/go-snaps/internal/difflib"
)

const (
Expand Down
4 changes: 2 additions & 2 deletions snaps/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"strings"
"testing"

"github.com/gkampitakis/go-snaps/snaps/internal/colors"
"github.com/gkampitakis/go-snaps/snaps/internal/test"
"github.com/gkampitakis/go-snaps/internal/colors"
"github.com/gkampitakis/go-snaps/internal/test"
)

var a = `Proin justo libero, pellentesque sit amet scelerisque ut, sollicitudin non tortor.
Expand Down
110 changes: 110 additions & 0 deletions snaps/matchJSON.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package snaps

import (
"encoding/json"
"errors"

"github.com/tidwall/gjson"
"github.com/tidwall/pretty"
)

var (
jsonOptions = &pretty.Options{
SortKeys: true,
Indent: " ",
}
errInvalidJSON = errors.New("invalid json")
)

/*
MatchJSON verifies the input matches the most recent snap file.
Input can be a valid json string or []byte or whatever value can be passed
successfully on `json.Marshal`.
MatchJSON(t, `{"user":"mock-user","age":10,"email":"mock@email.com"}`)
MatchJSON(t, []byte(`{"user":"mock-user","age":10,"email":"mock@email.com"}`))
MatchJSON(t, User{10, "mock-email"})
matchers is placeholder for now.
*/
func MatchJSON(t testingT, input interface{}, matchers ...interface{}) {
t.Helper()

dir, snapPath := snapDirAndName()
testID := testsRegistry.getTestID(t.Name(), snapPath)

j, err := validateJSON(input)
if err != nil {
handleError(t, err)
return
}

snapshot := takeJSONSnapshot(j)
if err != nil {
handleError(t, err)
return
}
prevSnapshot, err := getPrevSnapshot(testID, snapPath)
if errors.Is(err, errSnapNotFound) {
if isCI {
handleError(t, err)
return
}

err := addNewSnapshot(testID, snapshot, dir, snapPath)
if err != nil {
handleError(t, err)
return
}

t.Log(addedMsg)
testEvents.register(added)
return
}
if err != nil {
handleError(t, err)
return
}

diff := prettyDiff(prevSnapshot, snapshot)
if diff == "" {
testEvents.register(passed)
return
}

if !shouldUpdate {
handleError(t, diff)
return
}

if err = updateSnapshot(testID, snapshot, snapPath); err != nil {
handleError(t, err)
return
}

t.Log(updatedMsg)
testEvents.register(updated)
}

func validateJSON(input interface{}) ([]byte, error) {
switch j := input.(type) {
case string:
if !gjson.Valid(j) {
return nil, errInvalidJSON
}

return []byte(j), nil
case []byte:
if !gjson.ValidBytes(j) {
return nil, errInvalidJSON
}

return j, nil
default:
return json.Marshal(input)
}
}

func takeJSONSnapshot(b []byte) string {
return string(pretty.PrettyOptions(b, jsonOptions))
}
Loading

0 comments on commit 864d5ba

Please sign in to comment.