Issue with testing and mock objects

Greetings

I am writing a piece of code that rely on an api with code like this

package awesome

struct HttpError {
	code int
}

func (e *HttpError) GetCode() int {
	return e.code
}


struct Client {}

func (c *Client) AwesomeOp() error {
	// maybe 404
	return &HttpError{code:404}
	// or maybe other things will happen
}

My code uses AwesomeOp() and perform some operation when I get a 404 error like this

type myAwesomeInterface interface {
	AwesomeOp() error
}

type Bar struct {
	// I can totally use awesome.Client here in production
	// and mock my own object with the same interface
	client myAwesomeInterface
}

func (b *Bar) Foo() {
	err := b.client.AwesomeOp()
	if httpErr, isHttpErr := err.(awesome.HttpError); isHttpErr {
		errorCode := httpErr.GetCode()
		if errorCode == 404 {
			// THIS is untestable since I can't create my own HttpError with error == 404
		}
	}
}

I found that the 404 code path is impossible to test because my mock object cannot create a 404 HttpError since the httpError.code field is private. I considered doing this:

type myHttpError interface {
	GetCode() int
}

func (b *Bar) TestableButBrokenFoo() {
	err := b.client.AwesomeOp()
	if httpErr, isHttpErr := err.(myHttpError); isHttpErr {
		errorCode := httpErr.GetCode()
		if errorCode == 404 {
			// untestable since I can't create my own
		}
	}
}

This implementation is now functional and testable but it is incorrect. If the team for awesome package change the signature of GetCode() to something else. My code would still build, the unit tests would even pass without giving me an issue.

Is there a way to claim awesome.HttpError must implement myHttpError interface so I can discover that they change the method and break my code at build time? That’s ideally what I want.

yep:

var _ myHttpError = ((*awesome.HttpError)(nil)

hth,
-s

1 Like

thanks
That will certainly work.
I guess I would have to put a function like this somewhere in my application that does a bunch of claims like this.

Just realized: would this line get optimized out by a compiler since the variable is unused?
Should I be naming this variable and actually do something with it?

you can put that line at the top level of your foo.go files.
the compiler/linker may elide it from the binary but what you are interested in is that the line compiles. (all the better if it doesn’t end up in the binary!)

1 Like

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