Golang返回指向接口的指针引发错误

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
", err)
    }
}

Here's a link to the Go playground

Where am I going wrong?

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
", 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
", err2)
}

Try the examples on the Go Playground.