2016-09-27 5 views
4

Я полностью смущен, выясняя, как я могу издеваться над функцией, не используя никаких дополнительных пакетов, таких как golang/mock. Я просто пытаюсь научиться этому, но не могу найти много достойных онлайн-ресурсов.Стыковка функций в Голанге для проверки моих маршрутов http

По существу, I 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 
} 

Но это a ffects все другие тесты и т.д.

Я надеюсь, что кто-то может просто объяснить, что лучший способ для имитации функции FindAndCompleteOperation, так что я могу проверить маршруты, не полагаясь на хранилищу и т.д.

+0

У меня есть ответ к аналогичному вопросу здесь: http://stackoverflow.com/questions/19167970/mock-functions-in-go/19168875#19168875 Длинная история short: вам нужно ввести функцию FindAndCompleteOperation(), чтобы издеваться над ней. – weberc2

+0

Вот еще один подход с полным примером, который должен прояснить, как можно написать тестовый код в Go: https://stackoverflow.com/a/48206430/828366 –

+0

Возможный дубликат [Mock functions in Go] (https://stackoverflow.com/questions/19167970/mock-functions-in-go) –

ответ

2

У меня есть другой актуальный, более информативный ответ на аналогичный вопрос here, но вот ответ для вашего конкретного сценария:

Обновление SetupRouter() функции взять функцию, которая может быть либо реальной FindAndCompleteOperation функция или функция заглушки:

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"]) 
    } 
} 
+0

Не могли бы вы также поставить тест? –

+0

@JennyBlunt Test добавлен. – weberc2

+0

Попадая туда медленно, это ломает меня. Я не знаю, почему :(В моем случае мне нужно протестировать весь маршрут. Теперь я обновил свои функции, но до сих пор не вижу, что мне нужно вставить в тест в моем примере. –

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 */) 
} 
+0

Спасибо, можно было бы расширить это в контексте моего текущего теста. Я все еще не уверен, как все это сочетается. J –

+1

TLDR; измените свой код, чтобы сделать его «макетным», потому что часть кода показала мне, что нет. Вы не можете издеваться над чем-либо, если не создаете зависимости, которые являются интерфейсами при создании ваших обработчиков. Это ничем не отличается от других статически типизированных языков. В Java это будет точно то же самое, ваши методы должны принимать интерфейсы как аргумент вместо конкретных типов, и вы не должны полагаться на типы, которые не вводятся. – mpm

+0

Хорошо. Я понимаю это. Но это именно то, что мне нужно. Я не знаю, как на самом деле реализовать. Если бы я знал, я бы не потратил время на задание вопроса. –

 Смежные вопросы

  • Нет связанных вопросов^_^