如何在golang中测试映射对象

huangapple go评论87阅读模式
英文:

How to Test mapped Objects in golang

问题

我尝试在我的Go代码中实现一些单元测试,并发现模拟方法的主题相当困难。
我有以下示例,希望你能帮助我 如何在golang中测试映射对象

在第一层中,我有以下代码:

package api

import (
	"fmt"
    "core"
)

type createUserDTO struct {
	Id string
}

func ApiMethod() {
	fmt.Println("some incoming api call wit user")
	incomingUserData := &createUserDTO{Id: "testId"}
	mapedUser := incomingUserData.mapUser()
	mapedUser.Create()
}

func (createUserDTO *createUserDTO) mapUser() core.User {
	return &core.UserCore{Id: createUserDTO.Id}
}

第二层有以下代码:

package core

import (
	"fmt"
)

type CoreUser struct{ Id string }

type User interface {
	Create()
}

func (user CoreUser) Create() {
	fmt.Println("Do Stuff")
}

我的问题是,如何在不测试核心包的情况下测试api包中的每个公共方法,特别是Create()方法。

英文:

I try to implement some unit testing in my go code and find the topic of mocking method quite difficult.
I have the following example where I hope you can help me 如何在golang中测试映射对象

On the first layer I have the following code:

package api

import (
	"fmt"
    "core"
)

type createUserDTO struct {
	Id string
}

func ApiMethod() {
	fmt.Println("some incoming api call wit user")
	incomingUserData := &createUserDTO{Id: "testId"}
	mapedUser := incomingUserData.mapUser()
	mapedUser.Create()
}

func (createUserDTO *createUserDTO) mapUser() core.User {
	return &core.UserCore{Id: createUserDTO.Id}
}

The second layer has the following code:

package core

import (
	"fmt"
)

type CoreUser struct{ Id string }

type User interface {
	Create()
}

func (user CoreUser) Create() {
	fmt.Println("Do Stuff")
}

My question now is, how do I test every public method in the api package without testing the core package. Especially the method Create().

答案1

得分: 1

根据评论,我创建了一个简单的GitHub存储库,展示了我通常如何在Go中组织项目结构。该存储库目前不考虑测试部分,但使用这种项目结构应该很容易插入测试。

让我们从一般文件夹布局开始:

  • controllers(控制器)
  • services(服务)
  • db(数据库)
  • dto(数据传输对象)
  • models(模型)

现在,让我们看看相关的文件。

models/user.go
package models

import "gorm.io/gorm"

type User struct {
	*gorm.Model
	Id string `gorm:"primaryKey"`
}

func NewUser(id string) *User {
	return &User{Id: id}
}

这里是简单的结构定义。

dto/user.go
package dto

import "time"

type UserDTO struct {
	Id      string    `json:"id"`
	AddedOn time.Time `json:"added_on"`
}

func NewUserDTO(id string) *UserDTO {
	return &UserDTO{Id: id}
}

我们用一个虚拟的AddedOn字段丰富了模型结构,这只是为了演示而需要。

db/user.go
package db

import (
	"gorm.io/gorm"

	"userapp/models"
)

type UserDb struct {
	Conn *gorm.DB
}

type UserDbInterface interface {
	SaveUser(user *models.User) error
}

func (u *UserDb) SaveUser(user *models.User) error {
	if dbTrn := u.Conn.Create(user); dbTrn.Error != nil {
		return dbTrn.Error
	}
	return nil
}

在这里,我们为使用User存储库定义了一个接口。在我们的测试中,我们可以提供一个模拟对象,而不是UserDb结构的实例。

services/user.go
package services

import (
	"time"

	"userapp/db"
	"userapp/dto"
	"userapp/models"
)

type UserService struct {
	DB db.UserDbInterface
}

type UserServiceInterface interface {
	AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error)
}

func NewUserService(db db.UserDbInterface) *UserService {
	return &UserService{
		DB: db,
	}
}

func (u *UserService) AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error) {
	// 这里可以编写复杂的逻辑
	user := models.NewUser(inputReq.Id)

	// 调用数据库存储库
	if err := u.DB.SaveUser(user); err != nil {
		return nil, err
	}

	inputReq.AddedOn = time.Now()

	return inputReq, nil
}

这是连接表示层和底层存储库之间的桥梁层。

controllers/user.go
package controllers

import (
	"encoding/json"
	"io"
	"net/http"

	"userapp/dto"
	"userapp/services"
)

type UserController struct {
	US services.UserServiceInterface
}

func NewUserController(userService services.UserServiceInterface) *UserController {
	return &UserController{
		US: userService,
	}
}

func (u *UserController) Save(w http.ResponseWriter, r *http.Request) {
	reqBody, err := io.ReadAll(r.Body)
	if err != nil {
		panic(err)
	}
	r.Body.Close()

	var userReq dto.UserDTO

	json.Unmarshal(reqBody, &userReq)

	userRes, err := u.US.AddUser(&userReq)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		json.NewEncoder(w).Encode(err)
		return
	}

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(userRes)
}

在这里,我们定义了控制器(通过依赖注入)使用UserService结构。

你可以在我的GitHub存储库中找到所有内容:GitHub。如果有任何疑问,请告诉我。

英文:

Based on the comments, I put together a trivial GitHub repository to show how I usually deal with structuring projects in Go. The repository doesn't take into consideration the test part for now but it should be pretty easy to insert them with this project structure.
Let's start with the general folders' layout:

  • controllers
  • services
  • db
  • dto
  • models

Now, let's see the relevant files.

models/user.go
package models

import "gorm.io/gorm"

type User struct {
	*gorm.Model
	Id string `gorm:"primaryKey"`
}

func NewUser(id string) *User {
	return &User{Id: id}
}

Simple struct definition here.

dto/user.go
package dto

import "time"

type UserDTO struct {
	Id      string    `json:"id"`
	AddedOn time.Time `json:"added_on"`
}

func NewUserDTO(id string) *UserDTO {
	return &UserDTO{Id: id}
}

We enrich the model struct with a dummy AddedOn field which needs only for the sake of the demo.

db/user.go
package db

import (
	"gorm.io/gorm"

	"userapp/models"
)

type UserDb struct {
	Conn *gorm.DB
}

type UserDbInterface interface {
	SaveUser(user *models.User) error
}

func (u *UserDb) SaveUser(user *models.User) error {
	if dbTrn := u.Conn.Create(user); dbTrn.Error != nil {
		return dbTrn.Error
	}
	return nil
}

Here, we define an interface for using the User repository. In our tests, we can provide a mock instead of an instance of the UserDb struct.

services/user.go
package services

import (
	"time"

	"userapp/db"
	"userapp/dto"
	"userapp/models"
)

type UserService struct {
	DB db.UserDbInterface
}

type UserServiceInterface interface {
	AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error)
}

func NewUserService(db db.UserDbInterface) *UserService {
	return &UserService{
		DB: db,
	}
}

func (u *UserService) AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error) {
	// here you can write complex logic
	user := models.NewUser(inputReq.Id)

	// invoke db repo
	if err := u.DB.SaveUser(user); err != nil {
		return nil, err
	}

	inputReq.AddedOn = time.Now()

	return inputReq, nil
}

This is the layer that bridges connections between the presentation layer and the underlying repositories.

controllers/user.go
package controllers

import (
	"encoding/json"
	"io"
	"net/http"

	"userapp/dto"
	"userapp/services"
)

type UserController struct {
	US services.UserServiceInterface
}

func NewUserController(userService services.UserServiceInterface) *UserController {
	return &UserController{
		US: userService,
	}
}

func (u *UserController) Save(w http.ResponseWriter, r *http.Request) {
	reqBody, err := io.ReadAll(r.Body)
	if err != nil {
		panic(err)
	}
	r.Body.Close()

	var userReq dto.UserDTO

	json.Unmarshal(reqBody, &userReq)

	userRes, err := u.US.AddUser(&userReq)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		json.NewEncoder(w).Encode(err)
		return
	}

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(userRes)
}

Here, we defined the controller that (through Dependency Injection) uses the UserService struct.

You can find everything in my repository on GitHub
Let me know if it clarifies a little bit.

huangapple
  • 本文由 发表于 2022年11月14日 06:10:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/74425152.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定