doucha5080 2016-07-26 13:20
浏览 84
已采纳

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?

  • 写回答

1条回答 默认 最新

  • duanjuete9206 2016-07-26 13:27
    关注

    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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 phython如何实现以下功能?查找同一用户名的消费金额合并—
  • ¥15 孟德尔随机化怎样画共定位分析图
  • ¥18 模拟电路问题解答有偿速度
  • ¥15 CST仿真别人的模型结果仿真结果S参数完全不对
  • ¥15 误删注册表文件致win10无法开启
  • ¥15 请问在阿里云服务器中怎么利用数据库制作网站
  • ¥60 ESP32怎么烧录自启动程序
  • ¥50 html2canvas超出滚动条不显示
  • ¥15 java业务性能问题求解(sql,业务设计相关)
  • ¥15 52810 尾椎c三个a 写蓝牙地址