如何在golang中测试映射对象

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

How to Test mapped Objects in golang

问题

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

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

  1. package api
  2. import (
  3. "fmt"
  4. "core"
  5. )
  6. type createUserDTO struct {
  7. Id string
  8. }
  9. func ApiMethod() {
  10. fmt.Println("some incoming api call wit user")
  11. incomingUserData := &createUserDTO{Id: "testId"}
  12. mapedUser := incomingUserData.mapUser()
  13. mapedUser.Create()
  14. }
  15. func (createUserDTO *createUserDTO) mapUser() core.User {
  16. return &core.UserCore{Id: createUserDTO.Id}
  17. }

第二层有以下代码:

  1. package core
  2. import (
  3. "fmt"
  4. )
  5. type CoreUser struct{ Id string }
  6. type User interface {
  7. Create()
  8. }
  9. func (user CoreUser) Create() {
  10. fmt.Println("Do Stuff")
  11. }

我的问题是,如何在不测试核心包的情况下测试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:

  1. package api
  2. import (
  3. "fmt"
  4. "core"
  5. )
  6. type createUserDTO struct {
  7. Id string
  8. }
  9. func ApiMethod() {
  10. fmt.Println("some incoming api call wit user")
  11. incomingUserData := &createUserDTO{Id: "testId"}
  12. mapedUser := incomingUserData.mapUser()
  13. mapedUser.Create()
  14. }
  15. func (createUserDTO *createUserDTO) mapUser() core.User {
  16. return &core.UserCore{Id: createUserDTO.Id}
  17. }

The second layer has the following code:

  1. package core
  2. import (
  3. "fmt"
  4. )
  5. type CoreUser struct{ Id string }
  6. type User interface {
  7. Create()
  8. }
  9. func (user CoreUser) Create() {
  10. fmt.Println("Do Stuff")
  11. }

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
  1. package models
  2. import "gorm.io/gorm"
  3. type User struct {
  4. *gorm.Model
  5. Id string `gorm:"primaryKey"`
  6. }
  7. func NewUser(id string) *User {
  8. return &User{Id: id}
  9. }

这里是简单的结构定义。

dto/user.go
  1. package dto
  2. import "time"
  3. type UserDTO struct {
  4. Id string `json:"id"`
  5. AddedOn time.Time `json:"added_on"`
  6. }
  7. func NewUserDTO(id string) *UserDTO {
  8. return &UserDTO{Id: id}
  9. }

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

db/user.go
  1. package db
  2. import (
  3. "gorm.io/gorm"
  4. "userapp/models"
  5. )
  6. type UserDb struct {
  7. Conn *gorm.DB
  8. }
  9. type UserDbInterface interface {
  10. SaveUser(user *models.User) error
  11. }
  12. func (u *UserDb) SaveUser(user *models.User) error {
  13. if dbTrn := u.Conn.Create(user); dbTrn.Error != nil {
  14. return dbTrn.Error
  15. }
  16. return nil
  17. }

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

services/user.go
  1. package services
  2. import (
  3. "time"
  4. "userapp/db"
  5. "userapp/dto"
  6. "userapp/models"
  7. )
  8. type UserService struct {
  9. DB db.UserDbInterface
  10. }
  11. type UserServiceInterface interface {
  12. AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error)
  13. }
  14. func NewUserService(db db.UserDbInterface) *UserService {
  15. return &UserService{
  16. DB: db,
  17. }
  18. }
  19. func (u *UserService) AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error) {
  20. // 这里可以编写复杂的逻辑
  21. user := models.NewUser(inputReq.Id)
  22. // 调用数据库存储库
  23. if err := u.DB.SaveUser(user); err != nil {
  24. return nil, err
  25. }
  26. inputReq.AddedOn = time.Now()
  27. return inputReq, nil
  28. }

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

controllers/user.go
  1. package controllers
  2. import (
  3. "encoding/json"
  4. "io"
  5. "net/http"
  6. "userapp/dto"
  7. "userapp/services"
  8. )
  9. type UserController struct {
  10. US services.UserServiceInterface
  11. }
  12. func NewUserController(userService services.UserServiceInterface) *UserController {
  13. return &UserController{
  14. US: userService,
  15. }
  16. }
  17. func (u *UserController) Save(w http.ResponseWriter, r *http.Request) {
  18. reqBody, err := io.ReadAll(r.Body)
  19. if err != nil {
  20. panic(err)
  21. }
  22. r.Body.Close()
  23. var userReq dto.UserDTO
  24. json.Unmarshal(reqBody, &userReq)
  25. userRes, err := u.US.AddUser(&userReq)
  26. if err != nil {
  27. w.WriteHeader(http.StatusInternalServerError)
  28. json.NewEncoder(w).Encode(err)
  29. return
  30. }
  31. w.WriteHeader(http.StatusCreated)
  32. json.NewEncoder(w).Encode(userRes)
  33. }

在这里,我们定义了控制器(通过依赖注入)使用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
  1. package models
  2. import "gorm.io/gorm"
  3. type User struct {
  4. *gorm.Model
  5. Id string `gorm:"primaryKey"`
  6. }
  7. func NewUser(id string) *User {
  8. return &User{Id: id}
  9. }

Simple struct definition here.

dto/user.go
  1. package dto
  2. import "time"
  3. type UserDTO struct {
  4. Id string `json:"id"`
  5. AddedOn time.Time `json:"added_on"`
  6. }
  7. func NewUserDTO(id string) *UserDTO {
  8. return &UserDTO{Id: id}
  9. }

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

db/user.go
  1. package db
  2. import (
  3. "gorm.io/gorm"
  4. "userapp/models"
  5. )
  6. type UserDb struct {
  7. Conn *gorm.DB
  8. }
  9. type UserDbInterface interface {
  10. SaveUser(user *models.User) error
  11. }
  12. func (u *UserDb) SaveUser(user *models.User) error {
  13. if dbTrn := u.Conn.Create(user); dbTrn.Error != nil {
  14. return dbTrn.Error
  15. }
  16. return nil
  17. }

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
  1. package services
  2. import (
  3. "time"
  4. "userapp/db"
  5. "userapp/dto"
  6. "userapp/models"
  7. )
  8. type UserService struct {
  9. DB db.UserDbInterface
  10. }
  11. type UserServiceInterface interface {
  12. AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error)
  13. }
  14. func NewUserService(db db.UserDbInterface) *UserService {
  15. return &UserService{
  16. DB: db,
  17. }
  18. }
  19. func (u *UserService) AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error) {
  20. // here you can write complex logic
  21. user := models.NewUser(inputReq.Id)
  22. // invoke db repo
  23. if err := u.DB.SaveUser(user); err != nil {
  24. return nil, err
  25. }
  26. inputReq.AddedOn = time.Now()
  27. return inputReq, nil
  28. }

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

controllers/user.go
  1. package controllers
  2. import (
  3. "encoding/json"
  4. "io"
  5. "net/http"
  6. "userapp/dto"
  7. "userapp/services"
  8. )
  9. type UserController struct {
  10. US services.UserServiceInterface
  11. }
  12. func NewUserController(userService services.UserServiceInterface) *UserController {
  13. return &UserController{
  14. US: userService,
  15. }
  16. }
  17. func (u *UserController) Save(w http.ResponseWriter, r *http.Request) {
  18. reqBody, err := io.ReadAll(r.Body)
  19. if err != nil {
  20. panic(err)
  21. }
  22. r.Body.Close()
  23. var userReq dto.UserDTO
  24. json.Unmarshal(reqBody, &userReq)
  25. userRes, err := u.US.AddUser(&userReq)
  26. if err != nil {
  27. w.WriteHeader(http.StatusInternalServerError)
  28. json.NewEncoder(w).Encode(err)
  29. return
  30. }
  31. w.WriteHeader(http.StatusCreated)
  32. json.NewEncoder(w).Encode(userRes)
  33. }

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:

确定