Skip to content

Commit

Permalink
refactor(test): improve failure messages in custom matchers (#422)
Browse files Browse the repository at this point in the history
display a readable diff of the "processed actual" vs "expected"
values

Fixes #421

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon authored Sep 30, 2019
1 parent 6c57b55 commit 37e48f8
Show file tree
Hide file tree
Showing 19 changed files with 164 additions and 112 deletions.
32 changes: 32 additions & 0 deletions testsupport/comparison.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package testsupport

import (
"reflect"

"github.com/davecgh/go-spew/spew"
. "github.com/onsi/ginkgo" // nolint: go-lint
"github.com/sergi/go-diff/diffmatchpatch"
)

type comparison struct {
actual string
expected string
diffs string
}

// compare compares the 'actual' vs 'expected' values and produces a 'comparison' report
func compare(actual interface{}, expected interface{}) comparison {
c := comparison{
actual: spew.Sdump(actual),
expected: spew.Sdump(expected),
}
if !reflect.DeepEqual(actual, expected) {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(c.actual, c.expected, true)
c.diffs = dmp.DiffPrettyText(diffs)
}
GinkgoT().Logf("actual:\n%s", c.actual)
GinkgoT().Logf("expected:\n%s", c.expected)
GinkgoT().Logf("diff:\n%s", c.diffs)
return c
}
20 changes: 20 additions & 0 deletions testsupport/comparison_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package testsupport_test

import (
"reflect"

"github.com/davecgh/go-spew/spew"
"github.com/sergi/go-diff/diffmatchpatch"
)

// compare compares the 'actual' vs 'expected' values and produces a 'comparison' report
func compare(actual interface{}, expected interface{}) string {
a := spew.Sdump(actual)
e := spew.Sdump(expected)
if reflect.DeepEqual(actual, expected) {
return ""
}
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(a, e, true)
return dmp.DiffPrettyText(diffs)
}
8 changes: 4 additions & 4 deletions testsupport/console_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ func (m *containMessageMatcher) Match(actual interface{}) (success bool, err err
return false, nil
}

func (m *containMessageMatcher) FailureMessage(actual interface{}) (message string) {
func (m *containMessageMatcher) FailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected console to contain message '%s' with level '%v'", m.msg, m.level)
}

func (m *containMessageMatcher) NegatedFailureMessage(actual interface{}) (message string) {
func (m *containMessageMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected console not to contain message '%s' with level '%v'", m.msg, m.level)
}

Expand Down Expand Up @@ -121,10 +121,10 @@ func (m *containAnyMessageMatcher) Match(actual interface{}) (success bool, err
return false, nil
}

func (m *containAnyMessageMatcher) FailureMessage(actual interface{}) (message string) {
func (m *containAnyMessageMatcher) FailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected console to contain a message at level '%v'", m.levels)
}

func (m *containAnyMessageMatcher) NegatedFailureMessage(actual interface{}) (message string) {
func (m *containAnyMessageMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected console not to contain a message at level '%v'", m.levels)
}
24 changes: 9 additions & 15 deletions testsupport/document_block_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ package testsupport

import (
"fmt"
"reflect"
"strings"

"github.com/bytesparadise/libasciidoc/pkg/parser"
"github.com/davecgh/go-spew/spew"

. "github.com/onsi/ginkgo"
"github.com/onsi/gomega/types"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)

// EqualDocumentBlock a custom matcher to verify that a document block matches the expectation
Expand All @@ -22,8 +18,9 @@ func EqualDocumentBlock(expected interface{}) types.GomegaMatcher {
}

type documentBlockMatcher struct {
expected interface{}
actual interface{}
expected interface{}
actual interface{}
comparison comparison
}

func (m *documentBlockMatcher) Match(actual interface{}) (success bool, err error) {
Expand All @@ -40,17 +37,14 @@ func (m *documentBlockMatcher) Match(actual interface{}) (success bool, err erro
if err != nil {
return false, err
}
if log.IsLevelEnabled(log.DebugLevel) {
GinkgoT().Logf("actual:\n%v", spew.Sdump(m.actual))
GinkgoT().Logf("expected:\n%v", spew.Sdump(m.expected))
}
return reflect.DeepEqual(m.expected, m.actual), nil
m.comparison = compare(m.actual, m.expected)
return m.comparison.diffs == "", nil
}

func (m *documentBlockMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected document blocks to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *documentBlockMatcher) FailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected document blocks to match:\n%s", m.comparison.diffs)
}

func (m *documentBlockMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected document blocks not to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *documentBlockMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected document blocks not to match:\n%s", m.comparison.diffs)
}
4 changes: 2 additions & 2 deletions testsupport/document_block_matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ var _ = Describe("document block assertions", func() {
},
},
}
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document blocks to match:\n\texpected: '%v'\n\tactual: '%v'", expected, obtained)))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document blocks not to match:\n\texpected: '%v'\n\tactual: '%v'", expected, obtained)))
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document blocks to match:\n%s", compare(obtained, expected))))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document blocks not to match:\n%s", compare(obtained, expected))))
})

It("should return error when invalid type is input", func() {
Expand Down
17 changes: 9 additions & 8 deletions testsupport/document_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package testsupport

import (
"fmt"
"reflect"
"strings"

"github.com/bytesparadise/libasciidoc/pkg/parser"
Expand All @@ -20,8 +19,9 @@ func EqualDocument(expected types.Document) gomegatypes.GomegaMatcher {
}

type documentMatcher struct {
expected types.Document
actual types.Document
expected types.Document
actual types.Document
comparison comparison
}

func (m *documentMatcher) Match(actual interface{}) (success bool, err error) {
Expand All @@ -34,13 +34,14 @@ func (m *documentMatcher) Match(actual interface{}) (success bool, err error) {
if err != nil {
return false, err
}
return reflect.DeepEqual(m.expected, m.actual), nil
m.comparison = compare(m.actual, m.expected)
return m.comparison.diffs == "", nil
}

func (m *documentMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected documents to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *documentMatcher) FailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected documents to match:\n%s", m.comparison.diffs)
}

func (m *documentMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected documents not to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *documentMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected documents not to match:\n%s", m.comparison.diffs)
}
4 changes: 2 additions & 2 deletions testsupport/document_matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ var _ = Describe("document assertions", func() {
},
},
}
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n\texpected: '%v'\n\tactual: '%v'", expected, obtained)))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n\texpected: '%v'\n\tactual: '%v'", expected, obtained)))
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n%s", compare(obtained, expected))))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n%s", compare(obtained, expected))))
})

It("should return error when invalid type is input", func() {
Expand Down
17 changes: 9 additions & 8 deletions testsupport/document_metadata_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package testsupport
import (
"context"
"fmt"
"reflect"

"github.com/bytesparadise/libasciidoc/pkg/renderer"
"github.com/bytesparadise/libasciidoc/pkg/types"
Expand All @@ -20,8 +19,9 @@ func HaveMetadata(expected interface{}) gomegatypes.GomegaMatcher {
}

type metadataMatcher struct {
expected interface{}
actual types.DocumentAttributes
expected interface{}
actual interface{}
comparison comparison
}

func (m *metadataMatcher) Match(actual interface{}) (success bool, err error) {
Expand All @@ -32,13 +32,14 @@ func (m *metadataMatcher) Match(actual interface{}) (success bool, err error) {
ctx := renderer.Wrap(context.Background(), source)
renderer.ProcessDocumentHeader(ctx)
m.actual = ctx.Document.Attributes
return reflect.DeepEqual(m.expected, m.actual), nil
m.comparison = compare(m.actual, m.expected)
return m.comparison.diffs == "", nil
}

func (m *metadataMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected document metadata to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *metadataMatcher) FailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected document metadata to match:\n%s", m.comparison.diffs)
}

func (m *metadataMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected document metadata not to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *metadataMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected document metadata not to match:\n%s", m.comparison.diffs)
}
10 changes: 7 additions & 3 deletions testsupport/document_metadata_matcher_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package testsupport_test

import (
"context"
"fmt"

"github.com/bytesparadise/libasciidoc/pkg/renderer"
"github.com/bytesparadise/libasciidoc/pkg/types"
"github.com/bytesparadise/libasciidoc/testsupport"

Expand Down Expand Up @@ -52,9 +54,11 @@ var _ = Describe("document metadata assertions", func() {
Expect(err).ToNot(HaveOccurred())
Expect(result).To(BeFalse())
// also verify the messages
obtained := types.DocumentAttributes{}
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata to match:\n\texpected: '%v'\n\tactual: '%v'", expected, obtained)))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata not to match:\n\texpected: '%v'\n\tactual: '%v'", expected, obtained)))
ctx := renderer.Wrap(context.Background(), actual)
renderer.ProcessDocumentHeader(ctx)
obtained := ctx.Document.Attributes
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata to match:\n%s", compare(obtained, expected))))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata not to match:\n%s", compare(obtained, expected))))
})

It("should return error when invalid type is input", func() {
Expand Down
17 changes: 9 additions & 8 deletions testsupport/document_preamble_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package testsupport
import (
"context"
"fmt"
"reflect"

"github.com/bytesparadise/libasciidoc/pkg/renderer"
"github.com/bytesparadise/libasciidoc/pkg/types"
Expand All @@ -20,8 +19,9 @@ func HavePreamble(expected types.Document) gomegatypes.GomegaMatcher {
}

type preambleMatcher struct {
expected types.Document
actual types.Document
expected types.Document
actual types.Document
comparison comparison
}

func (m *preambleMatcher) Match(actual interface{}) (success bool, err error) {
Expand All @@ -32,13 +32,14 @@ func (m *preambleMatcher) Match(actual interface{}) (success bool, err error) {
ctx := renderer.Wrap(context.Background(), doc)
renderer.IncludePreamble(ctx)
m.actual = ctx.Document
return reflect.DeepEqual(m.expected, m.actual), nil
m.comparison = compare(m.actual, m.expected)
return m.comparison.diffs == "", nil
}

func (m *preambleMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected document to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *preambleMatcher) FailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected documents to match:\n%s", m.comparison.diffs)
}

func (m *preambleMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected document not to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *preambleMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected documents not to match:\n%s", m.comparison.diffs)
}
18 changes: 9 additions & 9 deletions testsupport/document_preamble_matcher_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package testsupport_test

import (
"context"
"fmt"

"github.com/bytesparadise/libasciidoc/pkg/renderer"
"github.com/bytesparadise/libasciidoc/pkg/types"
"github.com/bytesparadise/libasciidoc/testsupport"

Expand Down Expand Up @@ -103,15 +105,13 @@ var _ = Describe("document preamble assertions", func() {
Expect(err).ToNot(HaveOccurred())
Expect(result).To(BeFalse())
// also verify messages
obtained := types.Document{
Attributes: types.DocumentAttributes{},
ElementReferences: types.ElementReferences{},
Footnotes: types.Footnotes{},
FootnoteReferences: types.FootnoteReferences{},
Elements: []interface{}{},
}
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document to match:\n\texpected: '%v'\n\tactual: '%v'", expected, obtained)))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document not to match:\n\texpected: '%v'\n\tactual: '%v'", expected, obtained)))
ctx := renderer.Wrap(context.Background(), actual)
renderer.IncludePreamble(ctx)
obtained := ctx.Document
GinkgoT().Logf(matcher.FailureMessage(actual))
GinkgoT().Logf(fmt.Sprintf("expected documents to match:\n%s", compare(obtained, expected)))
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n%s", compare(obtained, expected))))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n%s", compare(obtained, expected))))
})

It("should return error when invalid type is input", func() {
Expand Down
17 changes: 9 additions & 8 deletions testsupport/document_toc_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package testsupport
import (
"context"
"fmt"
"reflect"

"github.com/bytesparadise/libasciidoc/pkg/renderer"
"github.com/bytesparadise/libasciidoc/pkg/types"
Expand All @@ -20,8 +19,9 @@ func HaveTableOfContents(expected types.Document) gomegatypes.GomegaMatcher {
}

type tocMatcher struct {
expected types.Document
actual types.Document
expected types.Document
actual types.Document
comparison comparison
}

func (m *tocMatcher) Match(actual interface{}) (success bool, err error) {
Expand All @@ -32,13 +32,14 @@ func (m *tocMatcher) Match(actual interface{}) (success bool, err error) {
ctx := renderer.Wrap(context.Background(), doc)
renderer.IncludeTableOfContents(ctx)
m.actual = ctx.Document
return reflect.DeepEqual(m.expected, m.actual), nil
m.comparison = compare(m.actual, m.expected)
return m.comparison.diffs == "", nil
}

func (m *tocMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected document to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *tocMatcher) FailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected documents to match:\n%s", m.comparison.diffs)
}

func (m *tocMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("expected document not to match:\n\texpected: '%v'\n\tactual: '%v'", m.expected, m.actual)
func (m *tocMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return fmt.Sprintf("expected documents not to match:\n%s", m.comparison.diffs)
}
4 changes: 2 additions & 2 deletions testsupport/document_toc_matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ var _ = Describe("document table of contents assertions", func() {
// then
Expect(err).ToNot(HaveOccurred())
Expect(result).To(BeFalse())
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document to match:\n\texpected: '%v'\n\tactual: '%v'", expected, types.Document{})))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document not to match:\n\texpected: '%v'\n\tactual: '%v'", expected, types.Document{})))
Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n%s", compare(types.Document{}, expected))))
Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n%s", compare(types.Document{}, expected))))
})

It("should return error when invalid type is input", func() {
Expand Down
Loading

0 comments on commit 37e48f8

Please sign in to comment.