Assertion with error failing, but constructed exact same way

I’m starting to dip my toes into the http libraries and I’m having some problems whilst testing. I’m using the httptest library to force a StatusCode, but despite constructing and returning the same error, the test believes otherwise.

package base

import (
	"net/http"
	"encoding/json"
	"fmt"
)

// EastDevonResponse is the structured format of the response from eastdevon.gov.uk
type EastDevonResponse struct {
	Label   string `json:"label"`
	UPRN    string `json:"UPRN"`
	Results string `json:"Results"`
}

// GetBinDates returns an array of EastDevonResponses which match the postcode
func GetBinDates(url string) ([]EastDevonResponse, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		non200Err := fmt.Errorf("HTTP Response Error %d", http.StatusNotFound)
		return nil, non200Err
	}

	// Decode top level JSON array into a slice of structs
	payload := make([]EastDevonResponse, 0)
	if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
		return nil, err
	}
	return payload, nil
}

And the test

package base

import (
	"testing"
	"net/http/httptest"
	"net/http"
	"fmt"
)

func TestServerReturnsNon200(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNotFound)
	}))
	defer ts.Close()

	// Expect an error that matches the implementation
	_, err := GetBinDates(ts.URL)
	expectedErr := fmt.Errorf("HTTP Response Error %d", http.StatusNotFound)
	if err != expectedErr {
		t.Errorf("GetBinDates failed: got %v expected %v", err, expectedErr)
	}
}

When I run the test it fails with the following error:

=== RUN TestServerReturnsNon200
— FAIL: TestServerReturnsNon200 (0.00s)
client_test.go:20: GetBinDates failed: got HTTP Response Error 404 expected HTTP Response Error 404

Crazy, eh?

If you would like to run this, you can use the following url:

http://eastdevon.gov.uk/addressfinder?qtype=bins&term=ex5+3el

Hey @willis7,

It will work if you change this:

if err != expectedErr { ... }

To this:

if err.Error() != expectedErr.Error() { ... }

Edit: Just by the way, the reason that this is happening is because errors are also values. If you look at the code that errors.New uses to create a new error:

// New returns an error that formats as the given text.
func New(text string) error {
	return &errorString{text}
}

You can see that a new pointer to an error type is being returned, and comparing 2 errors created this way will be false in the same way that comparing this will also return false:

err1 := errors.New("I'm an error")
err2 := errors.New("I'm an error")
fmt.Println(err1 == err2)

type Object struct {
	Name string
}

o1 := &Object{"obj"}
o2 := &Object{"obj"}
fmt.Println(o1 == o2)
1 Like

Thank you, that did indeed work.

I dug deeper into the docs and noticed fmt.Errorf() returned an interface, not a struct like I first thought. Thanks for the guidance.

I’ll admit that when I first looked at your code I forgot why I wouldn’t work but then it came to me after a bit :stuck_out_tongue:

P.s You could technically use reflect.DeepEqual to compare something like errors if the code is more complicated, but I highly doubt it will rarely need to be used :stuck_out_tongue:

The following returns true, but I always avoid reflection if possible:

err1 := errors.New("I'm an error")
err2 := errors.New("I'm an error")
fmt.Println(reflect.DeepEqual(err1, err2))

Thank you, thats handy to know. It does feel like you’re cheating yourself out of the knowledge around the implementation with the DeepEqual though. I certainly learnt something valuable from this issue.

1 Like

I completely agree and personally I’ve never needed to use reflect.DeepEqual for anything before, but I just know that it’s there :stuck_out_tongue:

Edit: Not to say it couldn’t be useful for something like this:

m1 := map[string]int{"one": 1, "two": 2}
m2 := map[string]int{"one": 1, "two": 2}

fmt.Println(reflect.DeepEqual(m1, m2)) // true

// invalid operation: m1 == m2 (map can only be compared to nil)
// fmt.Println(m1 == m2)
1 Like

I’m surprised to see this method :

// New returns an error that formats as the given text.
func New(text string) error {
	return &errorString{text}
}

Why return a pointer ? The original object is returned anyway (not copied) and we know it’s not directly accessible because error is an interface. I’m curious to see the advantage to return object over a pointer in the case of interface.

It could be convention. It could also be that it’s a teeny tiny bit more efficient under the circumstances. We will get an interface value containing the pointer in question, which can be passed around as-is and will almost always only be used to compare against nil.

Had it returned an errorString{text} object instead, and declared the Error() method not as a pointer receiver, the interface would need to make a copy and then hold a pointer to the copy - because interface values always hold pointers to the underlying value. When passing the interface value somewhere or calling methods on it, the underlying value needs to be copied again.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.