Skip to content

Commit

Permalink
Merge pull request #209 from aubm/adds-support-for-match-xml
Browse files Browse the repository at this point in the history
Adds support for MatchXML
  • Loading branch information
Onsi Fakhouri authored Apr 30, 2017
2 parents 9b8c753 + a93bfc7 commit da36735
Show file tree
Hide file tree
Showing 18 changed files with 523 additions and 0 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ language: go
go:
- 1.6
- 1.7
- 1.8

install:
- go get -v ./...
Expand Down
14 changes: 14 additions & 0 deletions matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,20 @@ func MatchJSON(json interface{}) types.GomegaMatcher {
}
}

//MatchXML succeeds if actual is a string or stringer of XML that matches
//the expected XML. The XMLs are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
//The comparison of attribute values is not supported for go1.7 and before.
//For go1.7, the two following XMLs are equal:
//<person gender="female">
//
//<person gender="male">
func MatchXML(xml interface{}) types.GomegaMatcher {
return &matchers.MatchXMLMatcher{
XMLToMatch: xml,
}
}

//MatchYAML succeeds if actual is a string or stringer of YAML that matches
//the expected YAML. The YAML's are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
Expand Down
58 changes: 58 additions & 0 deletions matchers/match_xml_matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package matchers

import (
"encoding/xml"
"fmt"
"reflect"

"github.com/onsi/gomega/format"
)

type MatchXMLMatcher struct {
XMLToMatch interface{}
}

func (matcher *MatchXMLMatcher) Match(actual interface{}) (success bool, err error) {
actualString, expectedString, err := matcher.formattedPrint(actual)
if err != nil {
return false, err
}

aval := &xmlNode{}
eval := &xmlNode{}

if err := xml.Unmarshal([]byte(actualString), aval); err != nil {
return false, fmt.Errorf("Actual '%s' should be valid XML, but it is not.\nUnderlying error:%s", actualString, err)
}
if err := xml.Unmarshal([]byte(expectedString), eval); err != nil {
return false, fmt.Errorf("Expected '%s' should be valid XML, but it is not.\nUnderlying error:%s", expectedString, err)
}

aval.Clean()
eval.Clean()

return reflect.DeepEqual(aval, eval), nil
}

func (matcher *MatchXMLMatcher) FailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.formattedPrint(actual)
return fmt.Sprintf("Expected\n%s\nto match XML of\n%s", actualString, expectedString)
}

func (matcher *MatchXMLMatcher) NegatedFailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.formattedPrint(actual)
return fmt.Sprintf("Expected\n%s\nnot to match XML of\n%s", actualString, expectedString)
}

func (matcher *MatchXMLMatcher) formattedPrint(actual interface{}) (actualString, expectedString string, err error) {
var ok bool
actualString, ok = toString(actual)
if !ok {
return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
}
expectedString, ok = toString(matcher.XMLToMatch)
if !ok {
return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.XMLToMatch, 1))
}
return actualString, expectedString, nil
}
22 changes: 22 additions & 0 deletions matchers/match_xml_matcher_go1.8_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// +build go1.8

package matchers_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("MatchXMLMatcher Go 1.8", func() {

var (
sample_09 = readFileContents("test_data/xml/sample_09.xml")
sample_10 = readFileContents("test_data/xml/sample_10.xml")
)

Context("When passed stringifiables", func() {
It("should succeed if the XML matches", func() {
Ω(sample_09).ShouldNot(MatchXML(sample_10)) // same structures with different attribute values
})
})
})
85 changes: 85 additions & 0 deletions matchers/match_xml_matcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package matchers_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/matchers"
)

var _ = Describe("MatchXMLMatcher", func() {

var (
sample_01 = readFileContents("test_data/xml/sample_01.xml")
sample_02 = readFileContents("test_data/xml/sample_02.xml")
sample_03 = readFileContents("test_data/xml/sample_03.xml")
sample_04 = readFileContents("test_data/xml/sample_04.xml")
sample_05 = readFileContents("test_data/xml/sample_05.xml")
sample_06 = readFileContents("test_data/xml/sample_06.xml")
sample_07 = readFileContents("test_data/xml/sample_07.xml")
sample_08 = readFileContents("test_data/xml/sample_08.xml")
)

Context("When passed stringifiables", func() {
It("should succeed if the XML matches", func() {
Ω(sample_01).Should(MatchXML(sample_01)) // same XML
Ω(sample_01).Should(MatchXML(sample_02)) // same XML with blank lines
Ω(sample_01).Should(MatchXML(sample_03)) // same XML with different formatting
Ω(sample_01).ShouldNot(MatchXML(sample_04)) // same structures with different values
Ω(sample_01).ShouldNot(MatchXML(sample_05)) // different structures
Ω(sample_06).ShouldNot(MatchXML(sample_07)) // same xml names with different namespaces
Ω(sample_07).ShouldNot(MatchXML(sample_08)) // same structures with different values
})

It("should work with byte arrays", func() {
Ω([]byte(sample_01)).Should(MatchXML([]byte(sample_01)))
Ω([]byte(sample_01)).Should(MatchXML(sample_01))
Ω(sample_01).Should(MatchXML([]byte(sample_01)))
})
})

Context("when the expected is not valid XML", func() {
It("should error and explain why", func() {
success, err := (&MatchXMLMatcher{XMLToMatch: sample_01}).Match(`oops`)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("Actual 'oops' should be valid XML"))
})
})

Context("when the actual is not valid XML", func() {
It("should error and explain why", func() {
success, err := (&MatchXMLMatcher{XMLToMatch: `oops`}).Match(sample_01)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("Expected 'oops' should be valid XML"))
})
})

Context("when the expected is neither a string nor a stringer nor a byte array", func() {
It("should error", func() {
success, err := (&MatchXMLMatcher{XMLToMatch: 2}).Match(sample_01)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n <int>: 2"))

success, err = (&MatchXMLMatcher{XMLToMatch: nil}).Match(sample_01)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n <nil>: nil"))
})
})

Context("when the actual is neither a string nor a stringer nor a byte array", func() {
It("should error", func() {
success, err := (&MatchXMLMatcher{XMLToMatch: sample_01}).Match(2)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n <int>: 2"))

success, err = (&MatchXMLMatcher{XMLToMatch: sample_01}).Match(nil)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n <nil>: nil"))
})
})
})
20 changes: 20 additions & 0 deletions matchers/matcher_tests_suite_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package matchers_test

import (
"fmt"
"io/ioutil"
"os"
"testing"

. "github.com/onsi/ginkgo"
Expand Down Expand Up @@ -28,3 +31,20 @@ func Test(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Gomega Matchers")
}

func readFileContents(filePath string) []byte {
f := openFile(filePath)
b, err := ioutil.ReadAll(f)
if err != nil {
panic(fmt.Errorf("failed to read file contents: %v", err))
}
return b
}

func openFile(filePath string) *os.File {
f, err := os.Open(filePath)
if err != nil {
panic(fmt.Errorf("failed to open file: %v", err))
}
return f
}
6 changes: 6 additions & 0 deletions matchers/test_data/xml/sample_01.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
9 changes: 9 additions & 0 deletions matchers/test_data/xml/sample_02.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@


<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

1 change: 1 addition & 0 deletions matchers/test_data/xml/sample_03.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>
6 changes: 6 additions & 0 deletions matchers/test_data/xml/sample_04.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<note>
<to>Tove</to>
<from>John</from>
<heading>Doe</heading>
<body>Don't forget me this weekend!</body>
</note>
Loading

0 comments on commit da36735

Please sign in to comment.