Как издеваться над способом стороннего пакета в Голанге - PullRequest
0 голосов
/ 31 мая 2019

У меня была простая функция, которая подключается к mongoDB и создает новый документ. Теперь, как мне издеваться над методами импортированного пакета Монго во время модульного тестирования.

Я пытался издеваться над GinContext с помощью monkeypatching.

Но не удалось продолжить насмешку над фактическим mongoClient при импорте пакета.

func CreateUser(c GinContext) {
    var userdetail UserDetails
    binderr := c.ShouldBindJSON(&userdetail)
    fmt.Println(binderr)
    if binderr != nil {
        c.JSON(500, gin.H{
            "message": "Input payload not matching",
            "error":   binderr,
        })
        return
    }

    //-- Client if of type *mongo.Client. 

        //-- How do I mock the Client.Database, Client.Database.Connection

    collection := Client.Database("demo").Collection("users")
    ctx, err1 := context.WithTimeout(context.Background(), 10*time.Second)
    if err1 != nil {
    }
    response, err2 := collection.InsertOne(ctx, userdetail)
    if err2 != nil {
        log.Println("Some error inserting the document")
    }
    fmt.Println(response.InsertedID)
    c.JSON(200, gin.H{
        "message": "User created successfully",
    })
}

Ожидается: я должен быть в состоянии смоделировать или заглушить Клиента и обеспечить фиктивную функциональность. Так же, как в nodeJS мы делаем

spyOn (клиент, 'Database'). And.return (что-то)

1 Ответ

0 голосов
/ 31 мая 2019

Каждый раз, когда я задаюсь вопросом «как смоделировать метод», это в основном связано с моей архитектурой кода. Неспособность легко протестировать какой-либо код в большинстве случаев означает, что код плохо спроектирован и / или слишком связан с используемыми библиотеками / средами. Здесь вы хотите смоделировать соединение с Mongo только потому, что ваш код слишком тесно связан с Mongo (в функции CreateUser). Рефакторинг может помочь вам протестировать ваш код (без подключения к Mongo).

Я испытал, что использование интерфейсов и внедрение зависимостей упрощает процесс тестирования в Go и проясняет архитектуру. Вот моя попытка помочь вам протестировать ваше приложение.

Рефакторинг кода

Сначала определите, что вы хотите сделать с интерфейсом . Здесь вы вставляете пользователей, поэтому давайте создадим интерфейс UserInserter с одним методом (Insert, чтобы вставить одного пользователя):

type UserInserter interface {
    Insert(ctx context.Context, userDetails UserDetails) (insertedID interface{}, err error)
}

В предоставленном вами коде вы используете только insertedID, поэтому он, вероятно, понадобится только как вывод этого метода Insert (и необязательная ошибка, если что-то пошло не так). insertedID здесь определяется как interface{}, но вы можете сменить его на что угодно.

Затем давайте изменим ваш метод CreateUser и добавим этот UserInserter в качестве параметра :

func CreateUser(c *gin.Context, userInserter UserInserter) {
    var userdetail UserDetails
    binderr := c.ShouldBindJSON(&userdetail)
    fmt.Println(binderr)
    if binderr != nil {
        c.JSON(500, gin.H{
            "message": "Input payload not matching",
            "error":   binderr,
        })
        return
    }

    // this is the modified part
    insertedID, err2 := userInserter.Insert(c, userdetail)
    if err2 != nil {
        log.Println("Some error inserting the document")
    }
    fmt.Println(insertedID)

    c.JSON(200, gin.H{
        "message": fmt.Sprintf("User %s created successfully", insertedID),
    })
}

Этот метод может быть изменен, но, чтобы избежать путаницы, я не буду его трогать.

userInserter.Insert(c, userdetail) заменяет здесь зависимость Монго в этом методе введением userInserter.

Теперь вы можете реализовать свой интерфейс UserInserter с выбранным бэкэндом (в вашем случае Mongo). Для вставки в Mongo требуется объект Collection (коллекция, в которую мы вставляем пользователя), поэтому давайте добавим это как атрибут :

type MongoUserInserter struct {
    collection *mongo.Collection
}

Далее следует реализация метода Insert (вызов метода InsertOne для *mongo.Collection):

func (i MongoUserInserter) Insert(ctx context.Context, userDetails UserDetails) (insertedID interface{}, err error) {
    response, err := i.collection.InsertOne(ctx, userDetails)
    return response.InsertedID, err
}

Эта реализация может быть в отдельном пакете и должна тестироваться отдельно.

После внедрения вы можете использовать MongoUserInserter в своем основном приложении, где Mongo - это бэкэнд. MongoUserInserter инициализируется в основной функции и вводится методом CreateUser. Настройки маршрутизатора были разделены (также для целей тестирования):

func setupRouter(userInserter UserInserter) *gin.Engine {
    router := gin.Default()

    router.POST("/createUser", func(c *gin.Context) {
        CreateUser(c, userInserter)
    })

    return router
}

func main() {
    client, _ := mongo.NewClient()
    collection := client.Database("demo").Collection("users")
    userInserter := MongoUserInserter{collection: collection}

    router := setupRouter(userInserter)
    router.Run(":8080")
}

Обратите внимание, что если когда-нибудь вы захотите изменить бэкэнд, вы будете только нужно изменить userInserter в основной функции!

Тесты

С точки зрения тестирования, теперь проще тестировать, потому что мы можем создать фальшивку UserInserter, например:

type FakeUserInserter struct{}

func (_ FakeUserInserter) Insert(ctx context.Context, userDetails UserDetails) (insertedID interface{}, err error) {
    return userDetails.Name, nil
}

(я предполагал, что здесь UserDetails имеет атрибут Name).

Если вы действительно хотите издеваться над этим интерфейсом, вы можете взглянуть на GoMock . Однако в этом случае я не уверен, что использование фиктивного фреймворка обязательно.

И теперь мы можем протестировать наш метод CreateUser с помощью простой среды тестирования HTTP (см. https://github.com/gin-gonic/gin#testing), без необходимости подключения к Mongo или насмешки над ним.

import (
    "bytes"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestCreateUser(t *testing.T) {
    userInserter := FakeUserInserter{}
    router := setupRouter(userInserter)

    w := httptest.NewRecorder()
    body := []byte(`{"name": "toto"}`)
    req, _ := http.NewRequest("POST", "/createUser", bytes.NewBuffer(body))
    router.ServeHTTP(w, req)

    assert.Equal(t, 200, w.Code)
    assert.Equal(t, `{"message":"User toto created successfully"}`, w.Body.String())
}

Обратите внимание, что это не освобождает также от проверки Insert метода MongoUserInserter, но отдельно: здесь этот тест охватывает CreateUser, а не Insert метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...