Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang return pointer to interface throws error

I have a basic function in Go that opens a file and tries to decode its JSON contents.

I am trying to extract the default json.NewDecoder() function so I can easily mock this in my tests.

However, my implementation seems to return an error:

cannot use json.NewDecoder (type func(io.Reader) *json.Decoder) as type decoderFactory in argument to NewConfig

Code:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "os"
)

type openFile func(name string) (*os.File, error)

type decoderFactory func(r io.Reader) decoder

type decoder interface {
    Decode(v interface{}) error
}

type Config struct {
    ConsumerKey,
    ConsumerSecret,
    AccessToken,
    AccessTokenSecret string
}

func NewConfig(open openFile, d decoderFactory) (*Config, error) {
    c := new(Config)
    file, err := open("some.file")
    if err != nil {
        return nil, fmt.Errorf("error opening config file")
    }
    defer file.Close()

    decoder := d(file)
    if err := decoder.Decode(&c); err != nil {
        return nil, fmt.Errorf("error decoding config JSON")
    }

    return c, nil
}

func main() {
    _, err := NewConfig(os.Open, json.NewDecoder)
    if err != nil {
        fmt.Fprintf(os.Stderr, "something bad happened: %v\n", err)
    }
}

Here's a link to the Go playground

Where am I going wrong?

like image 961
syscll Avatar asked Jul 26 '16 13:07

syscll


People also ask

What is [] interface {} Golang?

The interface type that specifies zero methods is known as the empty interface: interface{} An empty interface may hold values of any type. (Every type implements at least zero methods.) Empty interfaces are used by code that handles values of unknown type.

Is Golang pointer an interface?

Go's interface values are really a pair of pointers. When you put a concrete value into an interface value, one pointer starts pointing at the value. The second will now point to the implementation of the interface for the type of the concrete value.

How do I dereference a pointer in Golang?

The * and & operators In Go a pointer is represented using the * (asterisk) character followed by the type of the stored value. In the zero function xPtr is a pointer to an int . * is also used to “dereference” pointer variables. Dereferencing a pointer gives us access to the value the pointer points to.

How do I create an interface in Golang?

In Go, you can create an interface using the type keyword, followed by the name of the interface and the keyword interface. And, you can specify method signatures inside curly braces.


1 Answers

The json.NewDecoder() is a function with the following declaration:

func NewDecoder(r io.Reader) *Decoder

Its return type is *json.Decoder. json.Decoder is not an interface, it's a concrete type. And 2 function types are different if their return type is different: Spec: Function types:

A function type denotes the set of all functions with the same parameter and result types.

So you can't construct a new type returning an interface, and expect to be the same as json.NewDecoder, or that it'll accept the value json.NewDecoder.

But the "seemingly" easy fix is: define your decoderFactory to be a function type exactly what json.NewDecoder is:

type decoderFactory func(r io.Reader) *json.Decoder

This compiles, ok... but how to mock now?

How to mock now?

Of course in this form, you'll lose the possibility to mock json.NewDecoder() (because a "mocker" would have to return a value of type *json.Decoder and nothing else would be accepted). What to do then?

You have to use a different factory type. The factory type should be a function which returns an interface (of which you can provide different implementations), you were on the right track:

type MyDecoder interface {
    Decode(v interface{}) error
    // List other methods that you need from json.Decoder
}

type decoderFactory func(r io.Reader) MyDecoder

But you can't use json.NewEncoder as-is to pass as a value of decoderFactory. But fear not, it is very easy to create a function of type decoderFactory which will call json.NewEncoder() under the hood:

func jsonDecoderFact(r io.Reader) MyDecoder {
    return json.NewDecoder(r)
}

We're mocking the behaviour of json.Decoder, and not the json.NewDecoder() factory function.

Using this jsonDecoderFact():

_, err := NewConfig(os.Open, jsonDecoderFact)
if err != nil {
    fmt.Fprintf(os.Stderr, "something bad happened: %v\n", err)
}

This is valid and compiles, because jsonDecoderFact has exactly the same type as decoderFactory.

If you want to test / mock with a different implementation:

type TestDecoder struct {
    r io.Reader
}

func (t TestDecoder) Decode(v interface{}) error {
    // Test / mocking logic here
    return nil
}

func testDecoderFact(r io.Reader) MyDecoder {
    return TestDecoder{r}
}

Using it:

_, err2 := NewConfig(os.Open, testDecoderFact)
if err2 != nil {
    fmt.Fprintf(os.Stderr, "something bad happened: %v\n", err2)
}

Try the examples on the Go Playground.

like image 163
icza Avatar answered Oct 01 '22 23:10

icza