drwj4061 2016-09-27 11:21
浏览 94
已采纳

Golang中的模拟功能以测试我的http路由

I'm totally confused figuring out how I can mock a function, without using any additional packages like golang/mock. I'm just trying to learn how to do so but can't find many decent online resources.

Essentially, I followed this excellent article that explains how to use an interface to mock things.

As so, I've re-written the function I wanted to test. The function just inserts some data into datastore. My tests for that are ok - I can mock the function directly.

The issue I'm having is mocking it 'within' an http route I'm trying to test. Am using the Gin framework.

My router (simplified) looks like this:

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)
}

Which calls the UpdateOperation function:

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

So, I need to mock the FindAndCompleteOperation() function.

The main (simplified) functions looks like this:

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{}

To test the route that updates the operation, I have something like so:

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

Originally, I tried to cheat a bit and just tried something like this:

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

But that affects all the other tests etc.

I'm hoping someone can explain simply what the best way to mock the FindAndCompleteOperation function so I can test the routes, without relying on datastore etc.

  • 写回答

2条回答 默认 最新

  • dsrnwngq411594 2016-09-27 15:58
    关注

    I have another relevant, more informative answer to a similar question here, but here's an answer for your specific scenario:

    Update your SetupRouter() function to take a function that can either be the real FindAndCompleteOperation function or a stub function:

    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)
        }
    }
    

    Test/mocking example

    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"])
        }
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥20 wireshark抓不到vlan
  • ¥20 关于#stm32#的问题:需要指导自动酸碱滴定仪的原理图程序代码及仿真
  • ¥20 设计一款异域新娘的视频相亲软件需要哪些技术支持
  • ¥15 stata安慰剂检验作图但是真实值不出现在图上
  • ¥15 c程序不知道为什么得不到结果
  • ¥40 复杂的限制性的商函数处理
  • ¥15 程序不包含适用于入口点的静态Main方法
  • ¥15 素材场景中光线烘焙后灯光失效
  • ¥15 请教一下各位,为什么我这个没有实现模拟点击
  • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来