3

golang/mock のような追加のパッケージを使用せずに、関数をモックする方法を理解するのに完全に混乱しています。その方法を学ぼうとしていますが、適切なオンライン リソースがあまり見つかりません。

基本的に、インターフェイスを使用して物事をモックする方法を説明するこの優れた記事に従いました。

そのため、テストしたい関数を書き直しました。この関数は、データストアにデータを挿入するだけです。そのための私のテストは問題ありません-関数を直接モックできます。

私が抱えている問題は、テストしようとしている 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 関数をモックする最良の方法を誰かが簡単に説明してくれることを願っています.

4

2 に答える 2

2

同様の質問hereに対する別の関連性のある、より有益な回答がありますが、特定のシナリオに対する回答は次のとおりです。

関数を更新してSetupRouter()、実際のFindAndCompleteOperation関数またはスタブ関数のいずれかになる関数を取得します。

遊び場

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"])
    }
}
于 2016-09-27T15:58:33.583 に答える
1

ハンドラーでモックできないグローバルを使用すると、モックできません。グローバルがモック可能 (つまり、インターフェース型の変数として宣言されている) であるか、依存性注入を使用する必要があります。

func (oi OperationInfoer) FindAndCompleteOp(id string, report Report) error {...}

構造体のメソッドのように見えるので、少なくともその構造体をハンドラーに注入できるはずです。

type OperationInfoer interface {
   FindAndCompleteOp(id string, report Report) error 
} 

type ConcreteOperationInfoer struct { /* actual implementation */ }

func UpdateOperation(oi OperationInfoer) func(c *gin.Context) {
    return func (c *gin.Context){
        // the code
    }
}

その後、テストでモックが簡単になります:

UpdateOperation(mockOperationInfoer)(ginContext)

クロージャーの代わりに構造体を使用できます

type UpdateOperationHandler struct {
    Oi OperationInfoer
}
func (h UpdateOperationHandler ) UpdateOperation (c *gin.Context) {
    h.Oi.FindAndCompleteOp(/* code */ )
}
于 2016-09-27T14:25:26.323 に答える