package assert import ( "bufio" "bytes" "encoding/json" "fmt" "math" "reflect" "regexp" "runtime" "strings" "time" "unicode" "unicode/utf8" "github.com/davecgh/go-spew/spew" "github.com/pmezard/go-difflib/difflib" ) // TestingT is an interface wrapper around *testing.T type TestingT interface { Errorf(format string, args ...interface{}) } // Comparison a custom function that returns true on success and false on failure type Comparison func() (success bool) /* Helper functions */ // ObjectsAreEqual determines if two objects are considered equal. // // This function does no assertion of any kind. func ObjectsAreEqual(expected, actual interface{}) bool { if expected == nil || actual == nil { return expected == actual } return reflect.DeepEqual(expected, actual) } // ObjectsAreEqualValues gets whether two objects are equal, or if their // values are equal. func ObjectsAreEqualValues(expected, actual interface{}) bool { if ObjectsAreEqual(expected, actual) { return true } actualType := reflect.TypeOf(actual) if actualType == nil { return false } expectedValue := reflect.ValueOf(expected) if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { // Attempt comparison after type conversion return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) } return false } /* CallerInfo is necessary because the assert functions use the testing object internally, causing it to print the file:line of the assert method, rather than where the problem actually occured in calling code.*/ // CallerInfo returns an array of strings containing the file and line number // of each stack frame leading from the current test to the assert call that // failed. func CallerInfo() []string { pc := uintptr(0) file := "" line := 0 ok := false name := "" callers := []string{} for i := 0; ; i++ { pc, file, line, ok = runtime.Caller(i) if !ok { return nil } // This is a huge edge case, but it will panic if this is the case, see #180 if file == "" { break } parts := strings.Split(file, "/") dir := parts[len(parts)-2] file = parts[len(parts)-1] if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { callers = append(callers, fmt.Sprintf("%s:%d", file, line)) } f := runtime.FuncForPC(pc) if f == nil { break } name = f.Name() // Drop the package segments := strings.Split(name, ".") name = segments[len(segments)-1] if isTest(name, "Test") || isTest(name, "Benchmark") || isTest(name, "Example") { break } } return callers } // Stolen from the `go test` tool. // isTest tells whether name looks like a test (or benchmark, according to prefix). // It is a Test (say) if there is a character after Test that is not a lower-case letter. // We don't want TesticularCancer. func isTest(name, prefix string) bool { if !strings.HasPrefix(name, prefix) { return false } if len(name) == len(prefix) { // "Test" is ok return true } rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) return !unicode.IsLower(rune) } // getWhitespaceString returns a string that is long enough to overwrite the default // output from the go testing framework. func getWhitespaceString() string { _, file, line, ok := runtime.Caller(1) if !ok { return "" } parts := strings.Split(file, "/") file = parts[len(parts)-1] return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line))) } func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { if len(msgAndArgs) == 0 || msgAndArgs == nil { return "" } if len(msgAndArgs) == 1 { return msgAndArgs[0].(string) } if len(msgAndArgs) > 1 { return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) } return "" } // Indents all lines of the message by appending a number of tabs to each line, in an output format compatible with Go's // test printing (see inner comment for specifics) func indentMessageLines(message string, tabs int) string { outBuf := new(bytes.Buffer) for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ { if i != 0 { outBuf.WriteRune('\n') } for ii := 0; ii < tabs; ii++ { outBuf.WriteRune('\t') // Bizarrely, all lines except the first need one fewer tabs prepended, so deliberately advance the counter // by 1 prematurely. if ii == 0 && i > 0 { ii++ } } outBuf.WriteString(scanner.Text()) } return outBuf.String() } // Fail reports a failure through func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { message := messageFromMsgAndArgs(msgAndArgs...) errorTrace := strings.Join(CallerInfo(), "\n\r\t\t\t") if len(message) > 0 { t.Errorf("\r%s\r\tError Trace:\t%s\n"+ "\r\tError:%s\n"+ "\r\tMessages:\t%s\n\r", getWhitespaceString(), errorTrace, indentMessageLines(failureMessage, 2), message) } else { t.Errorf("\r%s\r\tError Trace:\t%s\n"+ "\r\tError:%s\n\r", getWhitespaceString(), errorTrace, indentMessageLines(failureMessage, 2)) } return false } // Implements asserts that an object is implemented by the specified interface. // // assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { interfaceType := reflect.TypeOf(interfaceObject).Elem() if !reflect.TypeOf(object).Implements(interfaceType) { return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...) } return true } // IsType asserts that the specified objects are of the same type. func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) { return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...) } return true } // Equal asserts that two objects are equal. // // assert.Equal(t, 123, 123, "123 and 123 should be equal") // // Returns whether the assertion was successful (true) or not (false). func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if !ObjectsAreEqual(expected, actual) { diff := diff(expected, actual) return Fail(t, fmt.Sprintf("Not equal: %#v (expected)\n"+ " != %#v (actual)%s", expected, actual, diff), msgAndArgs...) } return true } // EqualValues asserts that two objects are equal or convertable to the same types // and equal. // // assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") // // Returns whether the assertion was successful (true) or not (false). func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if !ObjectsAreEqualValues(expected, actual) { return Fail(t, fmt.Sprintf("Not equal: %#v (expected)\n"+ " != %#v (actual)", expected, actual), msgAndArgs...) } return true } // Exactly asserts that two objects are equal is value and type. // // assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") // // Returns whether the assertion was successful (true) or not (false). func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { aType := reflect.TypeOf(expected) bType := reflect.TypeOf(actual) if aType != bType { return Fail(t, fmt.Sprintf("Types expected to match exactly\n\r\t%v != %v", aType, bType), msgAndArgs...) } return Equal(t, expected, actual, msgAndArgs...) } // NotNil asserts that the specified object is not nil. // // assert.NotNil(t, err, "err should be something") // // Returns whether the assertion was successful (true) or not (false). func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if !isNil(object) { return true } return Fail(t, "Expected value not to be nil.", msgAndArgs...) } // isNil checks if a specified object is nil or not, without Failing. func isNil(object interface{}) bool { if object == nil { return true } value := reflect.ValueOf(object) kind := value.Kind() if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { return true } return false } // Nil asserts that the specified object is nil. // // assert.Nil(t, err, "err should be nothing") // // Returns whether the assertion was successful (true) or not (false). func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if isNil(object) { return true } return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) } var numericZeros = []interface{}{ int(0), int8(0), int16(0), int32(0), int64(0), uint(0), uint8(0), uint16(0), uint32(0), uint64(0), float32(0), float64(0), } // isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { if object == nil { return true } else if object == "" { return true } else if object == false { return true } for _, v := range numericZeros { if object == v { return true } } objValue := reflect.ValueOf(object) switch objValue.Kind() { case reflect.Map: fallthrough case reflect.Slice, reflect.Chan: { return (objValue.Len() == 0) } case reflect.Ptr: { if objValue.IsNil() { return true } switch object.(type) { case *time.Time: return object.(*time.Time).IsZero() default: return false } } } return false } // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // assert.Empty(t, obj) // // Returns whether the assertion was successful (true) or not (false). func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := isEmpty(object) if !pass { Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...) } return pass } // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // // if assert.NotEmpty(t, obj) { // assert.Equal(t, "two", obj[1]) // } // // Returns whether the assertion was successful (true) or not (false). func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := !isEmpty(object) if !pass { Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...) } return pass } // getLen try to get length of object. // return (false, 0) if impossible. func getLen(x interface{}) (ok bool, length int) { v := reflect.ValueOf(x) defer func() { if e := recover(); e != nil { ok = false } }() return true, v.Len() } // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // // assert.Len(t, mySlice, 3, "The size of slice is not 3") // // Returns whether the assertion was successful (true) or not (false). func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { ok, l := getLen(object) if !ok { return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...) } if l != length { return Fail(t, fmt.Sprintf("\"%s\" should have %d item(s), but has %d", object, length, l), msgAndArgs...) } return true } // True asserts that the specified value is true. // // assert.True(t, myBool, "myBool should be true") // // Returns whether the assertion was successful (true) or not (false). func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { if value != true { return Fail(t, "Should be true", msgAndArgs...) } return true } // False asserts that the specified value is false. // // assert.False(t, myBool, "myBool should be false") // // Returns whether the assertion was successful (true) or not (false). func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { if value != false { return Fail(t, "Should be false", msgAndArgs...) } return true } // NotEqual asserts that the specified values are NOT equal. // // assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") // // Returns whether the assertion was successful (true) or not (false). func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if ObjectsAreEqual(expected, actual) { return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) } return true } // containsElement try loop over the list check if the list includes the element. // return (false, false) if impossible. // return (true, false) if element was not found. // return (true, true) if element was found. func includeElement(list interface{}, element interface{}) (ok, found bool) { listValue := reflect.ValueOf(list) elementValue := reflect.ValueOf(element) defer func() { if e := recover(); e != nil { ok = false found = false } }() if reflect.TypeOf(list).Kind() == reflect.String { return true, strings.Contains(listValue.String(), elementValue.String()) } if reflect.TypeOf(list).Kind() == reflect.Map { mapKeys := listValue.MapKeys() for i := 0; i < len(mapKeys); i++ { if ObjectsAreEqual(mapKeys[i].Interface(), element) { return true, true } } return true, false } for i := 0; i < listValue.Len(); i++ { if ObjectsAreEqual(listValue.Index(i).Interface(), element) { return true, true } } return true, false } // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // // assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") // assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") // assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") // // Returns whether the assertion was successful (true) or not (false). func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { ok, found := includeElement(s, contains) if !ok { return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) } if !found { return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", s, contains), msgAndArgs...) } return true } // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // // assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") // assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") // assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") // // Returns whether the assertion was successful (true) or not (false). func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { ok, found := includeElement(s, contains) if !ok { return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) } if found { return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...) } return true } // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { result := comp() if !result { Fail(t, "Condition failed!", msgAndArgs...) } return result } // PanicTestFunc defines a func that should be passed to the assert.Panics and assert.NotPanics // methods, and represents a simple func that takes no arguments, and returns nothing. type PanicTestFunc func() // didPanic returns true if the function passed to it panics. Otherwise, it returns false. func didPanic(f PanicTestFunc) (bool, interface{}) { didPanic := false var message interface{} func() { defer func() { if message = recover(); message != nil { didPanic = true } }() // call the target function f() }() return didPanic, message } // Panics asserts that the code inside the specified PanicTestFunc panics. // // assert.Panics(t, func(){ // GoCrazy() // }, "Calling GoCrazy() should panic") // // Returns whether the assertion was successful (true) or not (false). func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { if funcDidPanic, panicValue := didPanic(f); !funcDidPanic { return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) } return true } // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // // assert.NotPanics(t, func(){ // RemainCalm() // }, "Calling RemainCalm() should NOT panic") // // Returns whether the assertion was successful (true) or not (false). func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { if funcDidPanic, panicValue := didPanic(f); funcDidPanic { return Fail(t, fmt.Sprintf("func %#v should not panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) } return true } // WithinDuration asserts that the two times are within duration delta of each other. // // assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") // // Returns whether the assertion was successful (true) or not (false). func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { dt := expected.Sub(actual) if dt < -delta || dt > delta { return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...) } return true } func toFloat(x interface{}) (float64, bool) { var xf float64 xok := true switch xn := x.(type) { case uint8: xf = float64(xn) case uint16: xf = float64(xn) case uint32: xf = float64(xn) case uint64: xf = float64(xn) case int: xf = float64(xn) case int8: xf = float64(xn) case int16: xf = float64(xn) case int32: xf = float64(xn) case int64: xf = float64(xn) case float32: xf = float64(xn) case float64: xf = float64(xn) default: xok = false } return xf, xok } // InDelta asserts that the two numerals are within delta of each other. // // assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) // // Returns whether the assertion was successful (true) or not (false). func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { af, aok := toFloat(expected) bf, bok := toFloat(actual) if !aok || !bok { return Fail(t, fmt.Sprintf("Parameters must be numerical"), msgAndArgs...) } if math.IsNaN(af) { return Fail(t, fmt.Sprintf("Actual must not be NaN"), msgAndArgs...) } if math.IsNaN(bf) { return Fail(t, fmt.Sprintf("Expected %v with delta %v, but was NaN", expected, delta), msgAndArgs...) } dt := af - bf if dt < -delta || dt > delta { return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...) } return true } // InDeltaSlice is the same as InDelta, except it compares two slices. func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) } actualSlice := reflect.ValueOf(actual) expectedSlice := reflect.ValueOf(expected) for i := 0; i < actualSlice.Len(); i++ { result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta) if !result { return result } } return true } // min(|expected|, |actual|) * epsilon func calcEpsilonDelta(expected, actual interface{}, epsilon float64) float64 { af, aok := toFloat(expected) bf, bok := toFloat(actual) if !aok || !bok { // invalid input return 0 } if af < 0 { af = -af } if bf < 0 { bf = -bf } var delta float64 if af < bf { delta = af * epsilon } else { delta = bf * epsilon } return delta } // InEpsilon asserts that expected and actual have a relative error less than epsilon // // Returns whether the assertion was successful (true) or not (false). func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { delta := calcEpsilonDelta(expected, actual, epsilon) return InDelta(t, expected, actual, delta, msgAndArgs...) } // InEpsilonSlice is the same as InEpsilon, except it compares two slices. func InEpsilonSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) } actualSlice := reflect.ValueOf(actual) expectedSlice := reflect.ValueOf(expected) for i := 0; i < actualSlice.Len(); i++ { result := InEpsilon(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta) if !result { return result } } return true } /* Errors */ // NoError asserts that a function returned no error (i.e. `nil`). // // actualObj, err := SomeFunction() // if assert.NoError(t, err) { // assert.Equal(t, actualObj, expectedObj) // } // // Returns whether the assertion was successful (true) or not (false). func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { if isNil(err) { return true } return Fail(t, fmt.Sprintf("Received unexpected error %q", err), msgAndArgs...) } // Error asserts that a function returned an error (i.e. not `nil`). // // actualObj, err := SomeFunction() // if assert.Error(t, err, "An error was expected") { // assert.Equal(t, err, expectedError) // } // // Returns whether the assertion was successful (true) or not (false). func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { message := messageFromMsgAndArgs(msgAndArgs...) return NotNil(t, err, "An error is expected but got nil. %s", message) } // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. // // actualObj, err := SomeFunction() // if assert.Error(t, err, "An error was expected") { // assert.Equal(t, err, expectedError) // } // // Returns whether the assertion was successful (true) or not (false). func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { message := messageFromMsgAndArgs(msgAndArgs...) if !NotNil(t, theError, "An error is expected but got nil. %s", message) { return false } s := "An error with value \"%s\" is expected but got \"%s\". %s" return Equal(t, errString, theError.Error(), s, errString, theError.Error(), message) } // matchRegexp return true if a specified regexp matches a string. func matchRegexp(rx interface{}, str interface{}) bool { var r *regexp.Regexp if rr, ok := rx.(*regexp.Regexp); ok { r = rr } else { r = regexp.MustCompile(fmt.Sprint(rx)) } return (r.FindStringIndex(fmt.Sprint(str)) != nil) } // Regexp asserts that a specified regexp matches a string. // // assert.Regexp(t, regexp.MustCompile("start"), "it's starting") // assert.Regexp(t, "start...$", "it's not starting") // // Returns whether the assertion was successful (true) or not (false). func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { match := matchRegexp(rx, str) if !match { Fail(t, fmt.Sprintf("Expect \"%v\" to match \"%v\"", str, rx), msgAndArgs...) } return match } // NotRegexp asserts that a specified regexp does not match a string. // // assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") // assert.NotRegexp(t, "^start", "it's not starting") // // Returns whether the assertion was successful (true) or not (false). func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { match := matchRegexp(rx, str) if match { Fail(t, fmt.Sprintf("Expect \"%v\" to NOT match \"%v\"", str, rx), msgAndArgs...) } return !match } // Zero asserts that i is the zero value for its type and returns the truth. func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) } return true } // NotZero asserts that i is not the zero value for its type and returns the truth. func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...) } return true } // JSONEq asserts that two JSON strings are equivalent. // // assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) // // Returns whether the assertion was successful (true) or not (false). func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { var expectedJSONAsInterface, actualJSONAsInterface interface{} if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil { return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...) } if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil { return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...) } return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...) } func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { t := reflect.TypeOf(v) k := t.Kind() if k == reflect.Ptr { t = t.Elem() k = t.Kind() } return t, k } // diff returns a diff of both values as long as both are of the same type and // are a struct, map, slice or array. Otherwise it returns an empty string. func diff(expected interface{}, actual interface{}) string { if expected == nil || actual == nil { return "" } et, ek := typeAndKind(expected) at, _ := typeAndKind(actual) if et != at { return "" } if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array { return "" } spew.Config.SortKeys = true e := spew.Sdump(expected) a := spew.Sdump(actual) diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ A: difflib.SplitLines(e), B: difflib.SplitLines(a), FromFile: "Expected", FromDate: "", ToFile: "Actual", ToDate: "", Context: 1, }) return "\n\nDiff:\n" + diff }