Каждый раз, когда я задаюсь вопросом «как смоделировать метод», это в основном связано с моей архитектурой кода. Неспособность легко протестировать какой-либо код в большинстве случаев означает, что код плохо спроектирован и / или слишком связан с используемыми библиотеками / средами. Здесь вы хотите смоделировать соединение с 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
метод.