Skip to content

Commit

Permalink
feat: support configurable snapshot dir and filename (#60)
Browse files Browse the repository at this point in the history
* feat: support configurable snapshot dir and filename

* fix: add absolute path on snapsDir

* fix: add examples and docs
  • Loading branch information
gkampitakis committed May 24, 2023
1 parent 24a2d6b commit f8620a8
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 53 deletions.
56 changes: 39 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
<img src="./images/new_snapshot.png" alt="App Preview New" width="500"/>
</p>


> matchJSON and matchers are still under development that means their API can change. Use with caution and please share feedback for improvements.
## Highlights
Expand All @@ -30,8 +29,9 @@
- [MatchSnapshot](#matchsnapshot)
- [MatchJSON](#matchjson)
- [Matchers](#matchers)
- [match.Any](#matchany)
- [match.Custom](#matchcustom)
- [match.Any](#matchany)
- [match.Custom](#matchcustom)
- [Configuration](#configuration)
- [Update Snapshots](#update-snapshots)
- [Clean obsolete Snapshots](#clean-obsolete-snapshots)
- [Skipping Tests](#skipping-tests)
Expand Down Expand Up @@ -92,7 +92,6 @@ 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.
Expand Down Expand Up @@ -120,7 +119,7 @@ JSON will be saved in snapshot in pretty format for more readability and determi
`MatchJSON`'s third argument can accept a list of matchers. Matchers are functions that can act
as property matchers and test values.

You can pass a path of the property you want to match and test.
You can pass a path of the property you want to match and test.

The path syntax is a series of keys separated by a dot. The dot and colon can be escaped with `\`.

Expand All @@ -131,7 +130,7 @@ Currently `go-snaps` has two build in matchers

#### match.Any

Any matcher acts as a placeholder for any value. It replaces any targeted path with a
Any matcher acts as a placeholder for any value. It replaces any targeted path with a
placeholder string.

```go
Expand Down Expand Up @@ -186,6 +185,28 @@ match.Custom("path",myFunc).

You can see more [examples](./examples/matchJSON_test.go#L93).

## Configuration

`go-snaps` allows passing configuration for overriding

- the directory where snapshots are stored, _relative or absolute path_
- the filename where snapshots are stored

```go
t.Run("snapshot tests", func(t *testing.T) {
snaps.WithConfig(snaps.Filename("my_custom_name"), snaps.Dir("my_dir")).MatchSnapshot(t, "Hello Word")

s := snaps.WithConfig(
snaps.Dir("my_dir"),
snaps.Filename("json_file"),
)

s.MatchJSON(t, `{"hello":"world"}`)
})
```

You can see more on [examples](/examples/matchSnapshot_test.go#L67)

## Update Snapshots

You can update your failing snapshots by setting `UPDATE_SNAPS` env variable to true.
Expand All @@ -198,6 +219,7 @@ If you don't want to update all failing snapshots, or you want to update one of
them you can you use the `-run` flag to target the test/s you want.

For more information for `go test` flags you can run

```go
go help testflag
```
Expand All @@ -211,15 +233,15 @@ go help testflag

`go-snaps` can identify obsolete snapshots.

In order to enable this functionality you need to use the `TestMain(t*testing.M)`
and call `snaps.Clean(t)`. This will also print a **Snapshot Summary**. (if running tests
In order to enable this functionality you need to use the `TestMain(t*testing.M)`
and call `snaps.Clean(t)`. This will also print a **Snapshot Summary**. (if running tests
with verbose flag `-v`)

If you want to remove the obsolete snap files and snapshots you can run
If you want to remove the obsolete snap files and snapshots you can run
tests with `UPDATE_SNAPS=true` env variable.

The reason for using `TestMain`, is because `go-snaps` needs to be sure that all tests
are finished so it can keep track which snapshots were not called.
The reason for using `TestMain`, is because `go-snaps` needs to be sure that all tests
are finished so it can keep track which snapshots were not called.

**Example:**

Expand All @@ -239,10 +261,10 @@ For more information around [TestMain](https://pkg.go.dev/testing#hdr-Main).
### 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
if the test was skipped or if it was removed. For that reason `go-snaps` exposes
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`
You can skip, or only run specific tests by using the `-run` flag. `go-snaps`
can identify which tests are being skipped and parse only the relevant tests
for obsolete snapshots.

Expand All @@ -259,7 +281,7 @@ For more information around [NO_COLOR](https://no-color.org).

## Snapshots Structure

Snapshots have the form
Snapshots have the form

```text
[ TestName - Number ]
Expand Down Expand Up @@ -294,12 +316,12 @@ This library used [Jest Snapshoting](https://jestjs.io/docs/snapshot-testing) an
## Notes

1. ⚠️ When running a specific test file by specifying a path
`go test ./my_test.go`, `go-snaps` can't track the path so it will mistakenly mark snapshots as obsolete.
`go test ./my_test.go`, `go-snaps` can't track the path so it will mistakenly mark snapshots as obsolete.

2. The order in which tests are written might not be the same order that snapshots are saved in the file.

3. If your snapshot data contain the termination characters `---` at the start of a line
and after a new line, `go-snaps` will "escape" them and save them as `/-/-/-/`. This
should not cause any diff issues (false-positives).
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. The snapshot artifact should be committed alongside code changes, and reviewed as part of your code review process
4 changes: 4 additions & 0 deletions examples/__snapshots__/custom_file.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

[TestMatchSnapshot/withConfig/should_allow_changing_filename - 1]
snapshot data
---
4 changes: 4 additions & 0 deletions examples/__snapshots__/matchSnapshot_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ map[string]interface {}{
lastRead: 0,
}
---

[TestMatchSnapshot/withConfig - 1]
this should use the default config
---
4 changes: 4 additions & 0 deletions examples/absolute_path/matchSnapshot_test.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

[TestMatchSnapshot/withConfig/should_allow_absolute_path - 1]
supporting absolute path
---
28 changes: 28 additions & 0 deletions examples/matchSnapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package examples
import (
"bytes"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/gkampitakis/go-snaps/snaps"
Expand Down Expand Up @@ -61,6 +63,32 @@ func TestMatchSnapshot(t *testing.T) {
t.Run(".*", func(t *testing.T) {
snaps.MatchSnapshot(t, "ignore regex patterns on names")
})

t.Run("withConfig", func(t *testing.T) {
t.Run("should allow changing filename", func(t *testing.T) {
snaps.WithConfig(
snaps.Filename("custom_file"),
).MatchSnapshot(t, "snapshot data")
})

t.Run("should allow changing dir", func(t *testing.T) {
s := snaps.WithConfig(snaps.Dir("testdata"))
s.MatchSnapshot(t, "snapshot with different dir name")
s.MatchSnapshot(t, "another one", 1, 10)
})

t.Run("should allow absolute path", func(t *testing.T) {
_, b, _, _ := runtime.Caller(0)
basepath := filepath.Dir(b)

snaps.WithConfig(snaps.Dir(basepath+"/absolute_path")).
MatchSnapshot(t, "supporting absolute path")
})

s := snaps.WithConfig(snaps.Dir("special_data"), snaps.Filename("different_name"))
s.MatchSnapshot(t, "different data than the rest")
snaps.MatchSnapshot(t, "this should use the default config")
})
}

func TestSimpleTable(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions examples/special_data/different_name.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

[TestMatchSnapshot/withConfig - 1]
different data than the rest
---
10 changes: 10 additions & 0 deletions examples/testdata/matchSnapshot_test.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

[TestMatchSnapshot/withConfig/should_allow_changing_dir - 1]
snapshot with different dir name
---

[TestMatchSnapshot/withConfig/should_allow_changing_dir - 2]
another one
int(1)
int(10)
---
29 changes: 28 additions & 1 deletion snaps/matchJSON.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ var (
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"})
MatchJSON also supports passing matchers as a third argument. Those matchers can act either as
validators or placeholders for data that might change on each invocation e.g. dates.
MatchJSON(t, User{created: time.Now(), email: "mock-email"}, match.Any("created"))
*/
func (c *config) MatchJSON(t testingT, input interface{}, matchers ...match.JSONMatcher) {
t.Helper()

matchJSON(c, t, input, matchers...)
}

/*
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
Expand All @@ -36,7 +56,14 @@ validators or placeholders for data that might change on each invocation e.g. da
*/
func MatchJSON(t testingT, input interface{}, matchers ...match.JSONMatcher) {
t.Helper()
dir, snapPath := snapDirAndName()

matchJSON(&defaultConfig, t, input, matchers...)
}

func matchJSON(c *config, t testingT, input interface{}, matchers ...match.JSONMatcher) {
t.Helper()

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

j, err := validateJSON(input)
Expand Down
52 changes: 39 additions & 13 deletions snaps/matchSnapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,53 @@ import (
"github.com/kr/pretty"
)

// MatchSnapshot verifies the values match the most recent snap file
//
// You can pass multiple values
//
// MatchSnapshot(t, 10, "hello world")
//
// or call MatchSnapshot multiples times inside a test
//
// MatchSnapshot(t, 10)
// MatchSnapshot(t, "hello world")
//
// The difference is the latter will create multiple entries.
/*
MatchSnapshot verifies the values match the most recent snap file
You can pass multiple values
MatchSnapshot(t, 10, "hello world")
or call MatchSnapshot multiples times inside a test
MatchSnapshot(t, 10)
MatchSnapshot(t, "hello world")
The difference is the latter will create multiple entries.
*/
func (c *config) MatchSnapshot(t testingT, values ...interface{}) {
t.Helper()

matchSnapshot(c, t, values...)
}

/*
MatchSnapshot verifies the values match the most recent snap file
You can pass multiple values
MatchSnapshot(t, 10, "hello world")
or call MatchSnapshot multiples times inside a test
MatchSnapshot(t, 10)
MatchSnapshot(t, "hello world")
The difference is the latter will create multiple entries.
*/
func MatchSnapshot(t testingT, values ...interface{}) {
t.Helper()

matchSnapshot(&defaultConfig, t, values...)
}

func matchSnapshot(c *config, t testingT, values ...interface{}) {
t.Helper()

if len(values) == 0 {
t.Log(colors.Sprint(colors.Yellow, "[warning] MatchSnapshot call without params\n"))
return
}

dir, snapPath := snapDirAndName()
dir, snapPath := snapDirAndName(c)
testID := testsRegistry.getTestID(t.Name(), snapPath)
snapshot := takeSnapshot(values)
prevSnapshot, err := getPrevSnapshot(testID, snapPath)
Expand Down
Loading

0 comments on commit f8620a8

Please sign in to comment.