testing - Golang 中的模拟函数来测试我的 http 路由

标签 testing go mocking go-gin

我完全不知道如何在不使用任何其他包(如 golang/mock)的情况下模拟一个函数。我只是想学习如何这样做,但找不到很多像样的在线资源。

本质上,我 followed this excellent article这解释了如何使用接口(interface)来模拟事物。

因此,我重新编写了我想测试的函数。该函数只是将一些数据插入数据存储区。我对此的测试没问题——我可以直接模拟该函数。

我遇到的问题是在我尝试测试的 http 路由“内”模拟它。我正在使用 Gin 框架。

我的路由器(简化版)如下所示:

func SetupRouter() *gin.Engine {

    r := gin.Default()
    r.Use(gin.Logger())
    r.Use(gin.Recovery())

    v1 := r.Group("v1")
    v1.PATCH("operations/:id", controllers.UpdateOperation)
}

其中调用了 UpdateOperation 函数:

func UpdateOperation(c *gin.Context) {
    id := c.Param("id")
    r := m.Response{}

    str := m.OperationInfoer{}
    err := m.FindAndCompleteOperation(str, id, r.Report)

    if err == nil {
      c.JSON(200, gin.H{
          "message": "Operation completed",
      })
    }
}

所以,我需要模拟 FindAndCompleteOperation() 函数。

主要(简化)函数如下所示:

func (oi OperationInfoer) FindAndCompleteOp(id string, report Report) error {
    ctx := context.Background()
    q := datastore.NewQuery("Operation").
        Filter("Unique_Id =", id).
        Limit(1)

    var ops []Operation

    if ts, err := db.Datastore.GetAll(ctx, q, &ops); err == nil {
        {
            if len(ops) > 0 {
                ops[0].Id = ts[0].ID()
                ops[0].Complete = true

                // Do stuff

                _, err := db.Datastore.Put(ctx, key, &o)
                if err == nil {
                  log.Print("OPERATION COMPLETED")
                }
            }
        }
    }

    err := errors.New("Not found")
    return err
}

func FindAndCompleteOperation(ri OperationInfoer, id string, report Report) error {
    return ri.FindAndCompleteOp(id, report)
}

type OperationInfoer struct{}

为了测试更新操作的路由,我有这样的东西:

FIt("Return 200, updates operation", func() {
    testRouter := SetupRouter()

    param := make(url.Values)
    param["access_token"] = []string{public_token}

    report := m.Report{}
    report.Success = true
    report.Output = "my output"

    jsonStr, _ := json.Marshal(report)
    req, _ := http.NewRequest("PATCH", "/v1/operations/123?"+param.Encode(), bytes.NewBuffer(jsonStr))

    resp := httptest.NewRecorder()
    testRouter.ServeHTTP(resp, req)

    Expect(resp.Code).To(Equal(200))

    o := FakeResponse{}
    json.NewDecoder(resp.Body).Decode(&o)
    Expect(o.Message).To(Equal("Operation completed"))
})

最初,我试图作弊,只是尝试了这样的事情:

m.FindAndCompleteOperation = func(string, m.Report) error {
  return nil
}

但这会影响所有其他测试等。

我希望有人能简单地解释模拟 FindAndCompleteOperation 函数的最佳方法是什么,这样我就可以测试路由,而不依赖于数据存储等。

最佳答案

我对类似问题有另一个相关的、信息量更大的答案 here ,但这是针对您的特定情况的答案:

更新您的 SetupRouter() 函数以获取可以是真正的 FindAndCompleteOperation 函数或 stub 函数的函数:

Playground

package main

import "github.com/gin-gonic/gin"

// m.Response.Report
type Report struct {
    // ...
}

// m.OperationInfoer
type OperationInfoer struct {
    // ...
}

type findAndComplete func(s OperationInfoer, id string, report Report) error

func FindAndCompleteOperation(OperationInfoer, string, Report) error {
    // ...
    return nil
}

func SetupRouter(f findAndComplete) *gin.Engine {
    r := gin.Default()
    r.Group("v1").PATCH("/:id", func(c *gin.Context) {
        if f(OperationInfoer{}, c.Param("id"), Report{}) == nil {
            c.JSON(200, gin.H{"message": "Operation completed"})
        }
    })
    return r
}

func main() {
    r := SetupRouter(FindAndCompleteOperation)
    if err := r.Run(":8080"); err != nil {
        panic(err)
    }
}

测试/模拟示例

package main

import (
    "encoding/json"
    "net/http/httptest"
    "strings"
    "testing"
)

func TestUpdateRoute(t *testing.T) {
    // build findAndComplete stub
    var callCount int
    var lastInfoer OperationInfoer
    var lastID string
    var lastReport Report
    stub := func(s OperationInfoer, id string, report Report) error {
        callCount++
        lastInfoer = s
        lastID = id
        lastReport = report
        return nil // or `fmt.Errorf("Err msg")` if you want to test fault path
    }

    // invoke endpoint
    w := httptest.NewRecorder()
    r := httptest.NewRequest(
        "PATCH",
        "/v1/id_value",
        strings.NewReader(""),
    )
    SetupRouter(stub).ServeHTTP(w, r)

    // check that the stub was invoked correctly
    if callCount != 1 {
        t.Fatal("Wanted 1 call; got", callCount)
    }
    if lastInfoer != (OperationInfoer{}) {
        t.Fatalf("Wanted %v; got %v", OperationInfoer{}, lastInfoer)
    }
    if lastID != "id_value" {
        t.Fatalf("Wanted 'id_value'; got '%s'", lastID)
    }
    if lastReport != (Report{}) {
        t.Fatalf("Wanted %v; got %v", Report{}, lastReport)
    }

    // check that the correct response was returned
    if w.Code != 200 {
        t.Fatal("Wanted HTTP 200; got HTTP", w.Code)
    }

    var body map[string]string
    if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
        t.Fatal("Unexpected error:", err)
    }
    if body["message"] != "Operation completed" {
        t.Fatal("Wanted 'Operation completed'; got %s", body["message"])
    }
}

关于testing - Golang 中的模拟函数来测试我的 http 路由,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39723345/

相关文章:

javascript - 我怎样才能 Jest 模拟一个图书馆?

java - Mockito thenReturn() 返回空值

testing - 回归测试是整个测试套件还是测试样本?

go - 如何在golang中间接传递接口(interface)

Python 模拟 vs unittest.mock 补丁

.net - 模拟对象 - 将所有方法声明为虚拟方法还是使用接口(interface)?

testing - 如何使用 InternIO 从测试套件运行特定测试用例

javascript - 使 expect 的两个部分 resolve promises

json - 如何在 Go1.9 中将 json 字符串解码为 sync.Map 而不是法线贴图?

go - 写入阅读器时分段上传到 s3