Checking the Type of an Interface in Go using Reflection
Recently while writing test cases for s3fs I came upon a situation where a function returns an interface, however the underlying type of the interface is dependant on the input. What I wanted to do was validate that the appropriate type was being returned for each supported input scenario.
First for context, here’s a look at the commandFromArgs function. The purpose of this function is to receive user input, for example “cd directory/”, and return the appropriate command executor to handle the command.
// commandFromArgs takes an arg slice and returns the appropriate command executor. func (s S3Handler) commandFromArgs(args []string) (ex command.Executor, err error) { switch args[0] { case command.CmdLs: ex = command.NewLs(s.s3, s.con) case command.CmdCd: ex = command.NewCd(s.s3, s.con, args[1:]) case command.CmdPwd: ex = command.NewPwd(s.con) case command.CmdClear: ex = command.NewClear() case command.CmdExit: ex = command.NewExit() default: err = errors.New("Unknown Command: " + args[0]) } return ex, err }
As you can see, a command.Executor interface type is being returned based on the input args slice, the first element of which is always the command name. This means the return value could be any number of types that implement the Executor interface, but I want to test to make sure the correct one is returned for it’s corresponding input.
Let’s setup the core test case logic and see what options we have:
func TestS3Handler_commandFromArgs(t *testing.T) { // Define the command types, and the expected Executor for each. cmds := []struct { name string expected command.Executor }{ {command.CmdLs, command.LsCommand{}}, {command.CmdCd, command.CdCommand{}}, {command.CmdPwd, command.PwdCommand{}}, {command.CmdClear, command.ClearCommand{}}, {command.CmdExit, command.ExitCommand{}}, } var s3 S3Handler // For each command, ensure the proper Executor is returned. for _, cmd := range cmds { c, err := s3.commandFromArgs([]string{cmd.name}) if err != nil { t.Fatal(err) } // Check the returned interface type and ensure equality with what's expected. // TODO: How do we check the type? } }
The first step is to create a slice of anonymous struct types, each of which contains a command name and the expected Executor type to be returned for each - these will be our test cases. For instance, the CmdLs command expects a LsCommand type to be returned, the CmdCd command expects a CdCommand, and so on.
Next we iterate over each of these test cases and call the commandFromArgs function that we’re testing, check for any errors, and then we come upon the point where we need to validate that the returned Executor is of the correct type.
Failed Attempts at Dynamic Type Assertion
Knowing that Go supports type assertion, my first instinct was to try some sort of dynamic type assertion. I tried a few variations of this, but they essentially all came down to something like so:
// THIS WON'T WORK if _, ok := c.(cmd.expected); !ok { t.Fatalf("Unexpected Executor returned for command[%v]: %v", cmd.name, c) }
However, my efforts here were immediately rejected by the compiler with the following error: cmd.expected is not a type. This makes sense, considering that cmd.expected has a type, but is not a type itself.
I figured there’s probably some nasty way of doing this by getting the type of cmd.expected, but better sense prevailed and I remembered that Go comes with a handy reflect package for situations like this that call for reflection.
Using Reflection
A quick read through the docs revealed the reflect.TypeOf function which returns the Type of an interface{} - exactly what we need for the test case.
With the help of the reflect package, the logic is actually quite simple and much more readable than my ugly and convoluted attempts at dynamic type assertion:
if reflect.TypeOf(c) != reflect.TypeOf(cmd.expected) { t.Fatalf("Unexpected Executor returned for command[%v]: %v", cmd.name, reflect.TypeOf(c)) }
Now when the tests run, the returned Executor is properly verified to be of the expected type.
As an added bonus, the readable name of the type can be printed when the test case fails using the same call to reflect.TypeOf(c):
--- FAIL: TestS3Handler_commandFromArgs (0.00s) s3_test.go:113: Unexpected Executor returned for command[cd]: command.PwdCommand
Word to the Wise
It is worth mentioning however that reflection in Go is generally to be avoided when possible, and many developers frown upon it’s usage. It doesn’t typically lend itself to clean or readable code, but for the purposes of the test cases above, it actually did provide a clean and concise means of validating the test cases.
Reflection is powerful but take care to use it only when necessary!