【问题标题】:Test wrap function with dependency injection使用依赖注入测试包装函数
【发布时间】:2018-12-21 18:26:58
【问题描述】:

我有这个功能,我需要在测试中模拟, 我能够使用 http 模拟包按预期模拟它,但现在我有正在调用的函数 到HttpReq 方法,这里我不能使用http mock 包 我阅读了依赖注入并尝试了一些方法,但我无法完全做到,

这是函数

type params struct {
    cs     string
    ci     string
    method string
    url    string
}

// I added this struct but not sure if it's needed ... probably for test purpose but not sure how to use it.

type Impl struct {
 client *http.Client
}

func (i *Impl) HttpReq(p *params) ([]byte, error) {
    httpClient := i.client
    req, err := http.NewRequest(p.method, p.url, nil)
    if err != nil {
        fmt.Sprintf(err)
    }
    req.SetBasicAuth(p.cs, p.ci)
    res, err := httpClient.Do(req)
    if err != nil {
        fmt.Sprintf(err)
    }
    t, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Sprintf(err)
    }
    defer res.Body.Close()
    return t, nil
}

这是我尝试过的

我已经创建了界面

type Req interface {
    HttpReq(params) ([]byte, error)
}

现在我创建了一个包含接口的结构体

type Service struct {
    req Req
}

这是我需要测试的功能

func (c *Service) execute(cli Connection , args []string) (error, []byte) {
    sk, err := c.doSomthing(cli, args)

    sc, err := c.doSometing2(serviceK, []string{"url", "cl", "ct"})

    cc := strings.Fields(serviceCredentials)

    // ----------Here is what I need to mock ----------
    t, err := c.req.HttpReq(params{cs: cc[1],
        ci:     cc[2],
        method: http.MethodPost,
        url:    cc[0],})
    return err, t
}

知道如何为这个函数运行测试吗???我为此苦苦挣扎。

【问题讨论】:

  • 旁白:不要为每个请求创建一个新的客户端:“客户端的传输通常具有内部状态(缓存的 TCP 连接),因此客户端应该被重用而不是根据需要创建。” golang.org/pkg/net/http/#Client
  • @Peter - 你建议把 is as global ?
  • 我建议你注入客户端。然后,您可以轻松地继续使用 httptest 包中的服务器。
  • @Peter - 但如果我注入它,调用者会一次又一次地创建它,不是吗?

标签: go dependency-injection


【解决方案1】:

独立于原始问题,您不应为每个请求创建新的 HTTP 客户端。客户端维护一个连接池,应该尽可能的复用。

您可以解决这个问题,并通过注入 HTTP 客户端继续使用现有的模拟服务器。

另请注意,问题中的接口定义与实现不匹配。这两个方法签名不一样:

HttpReq(params) ([]byte, error)  // Req
HttpReq(*params) ([]byte, error) // Impl

选择一个。我可能会在这里使用非指针类型。 And upper case initials are idiomatic in GoHTTPReq,不是HttpReq)。

将客户端添加到Impl类型并在HTTPReq中使用:

type Impl struct {
    client *http.Client
}

func (i *Impl) HTTPReq(p params) ([]byte, error) {
    req, err := http.NewRequest(p.method, p.url, nil)
    if err != nil {
        return nil, err
    }
    req.SetBasicAuth(p.cs, p.ci)

    res, err := i.client.Do(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    return ioutil.ReadAll(res.Body)
}

服务类型不必改变。

在测试中,只需将测试客户端注入Impl 值:

import (
    "context"
    "net"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestService_execute(t *testing.T) {
    var testHandler http.Handler // TODO

    srv := httptest.NewServer(testHandler)
    defer srv.Close()

    client := srv.Client()
    tp := client.Transport.(*http.Transport)

    // Direct all requests to the test server, no matter what the request URL is.
    tp.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
        // Note that we ignore the network and addr parameters, since these are 
        // derived from the request URL and are unrelated to the test server.

        srvAddr := srv.Listener.Addr()
        return (&net.Dialer{}).DialContext(ctx, srvAddr.Network(), srvAddr.String())
    }

    svc := &Service{
        req: &Impl{
            client: client,
        },
    }

    svc.execute(/* ... */)

    // assert request, response, etc.
}

【讨论】:

  • 嗨,彼得 1+,非常感谢!我尝试了您的代码,但缺少两个部分 1。`req: &Impl{ client: client, },this 不匹配,我收到错误 2。是否有示例示例只是在高级,(我一无所知:),如何测试 svc.execute(/* ... */)?再次感谢您的帮助
  • 错误是:cannot use Impl literal (type *Impl) as type Req in field value: *Impl does not implement Req (wrong type for HttpReq method) have HttpReq(*params) ([]byte, error) want HttpReq(params) ([]byte, error)
  • params != *params。方法签名必须完全匹配才能实现接口。
  • 您好,我已经采用了您的示例(请参阅更新后的问题),在您的示例中,您使用impl 并为其添加client,但我也有服务结构type Service struct { req Req } 因此它不起作用,显然我错过了一些东西,你能用Req 接口和Service 结构调整你的答案吗?这是我想使它工作的原因,因为目前我所拥有的(请参阅更新的问题)它不起作用:(,非常感谢!
  • @NinaS,我在问题的开头添加了一段关于方法不匹配的段落。
【解决方案2】:

由于 Service struct 已经有一个 req 接口,在测试期间使用满足 req 接口的 mock 初始化服务对象。 像这样
https://stackoverflow.com/a/53805535/3968921

【讨论】:

  • 好的,谢谢,我在搜索时已经看到了这篇文章,不确定我明白了。什么是 `type Implementation struct { }` 以及应该如何测试它在这种情况下的模拟实现是什么
  • 如果你能用解释和模拟测试来扩展你的例子会很棒,因为它应该在真实测试中,这是我目前缺少的东西......
猜你喜欢
  • 2015-05-12
  • 1970-01-01
  • 1970-01-01
  • 2018-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多