我很困惑弄清楚如何模拟一个函数,而不使用像golang / mock这样的任何其他软件包。我只是想学习如何这样做但却找不到很多不错的在线资源。
基本上,我followed this excellent article解释了如何使用界面来模拟事物。
因此,我重新编写了我想测试的功能。该函数只是将一些数据插入数据存储区。我的测试没问题 - 我可以直接模拟这个功能。
我遇到的问题是在'我试图测试的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函数的最佳方法,这样我就可以测试路由,而不依赖于数据存储区等。
答案 0 :(得分: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"])
}
}
答案 1 :(得分:1)
如果使用无法在处理程序中进行模拟的全局变量,则无法模拟。您的全局变量是可模拟的(即声明为接口类型的变量),或者您需要使用依赖注入。
func (oi OperationInfoer) FindAndCompleteOp(id string, report Report) error {...}
看起来像一个struct的方法,所以你应该能够将该结构注入到处理程序中,至少。
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 */ )
}