Golang中如何模拟函数进行测试

Golang中如何模拟函数进行测试 我想在对API进行测试时模拟函数。该API使用了不同包中的函数,但我希望使用testify/mock或其他Golang技术来模拟它们。 请建议我们是否可以实现这一点,是否可能模拟其他包中的函数。

我计划进行单元测试的API

func EmailTemplate(c echo.Context) error {

    wc  := core.GetWebContext(c)

    eTplUID := wc.Param("etpl_id")
    if len(eTplUID) == 0 {
            e := errors.Wrap(nil, "email template uid is required")
            wc.Logger().Error(e)
            //return e

    }

    //想要模拟此函数
    u, err := service.GetUserForRequest(wc)  
    if err != nil {
            //return wc.JSON(http.StatusUnauthorized, model.NewErrorResponse("error in fetching user details"))
    }

     // 想要模拟此函数
     appCtx := core.BuildAppContext(context.Background(), wc.Registry)

    // 想要模拟此函数
    err = service.DeleteEmailTemplate(appCtx, eTplUID, u.OrgID)
    if err != nil {
      return wc.JSON(http.StatusInternalServerError, model.NewErrorResponse().AddError(err))
    }
}

我计划用于测试上述API的API

func TestEmailTemplateDelete(t *testing.T) {

    e := echo.New()
    req := httptest.NewRequest(http.MethodGet, "/", nil)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    c.SetPath("/test/email_template/")
    c.SetParamNames("etpl_id")
    c.SetParamValues("12")

    if assert.NoError(t, EmailTemplateDelete(c)) {
            assert.Equal(t, http.StatusOK, rec.Code)
            //assert.Equal(t, userJSON, strings.TrimSpace(rec.Body.String()))
    }
}

我对此测试感到非常困惑,请建议测试方法。


更多关于Golang中如何模拟函数进行测试的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

你的意思是将端点或模型放在其他文件中吗?

更多关于Golang中如何模拟函数进行测试的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


非常感谢阿里! 😊

你好 Ali,

我是一名Go语言新手,刚从Linux管理员转行做Go开发。我一直在寻找一些好的资源来理解如何使用接口在Go中进行模拟测试,但网上的文章让我很困惑,因为我无法理清其中的模式。你能告诉我你是如何规划模拟测试的吗?我的意思是,应该有一个模式,比如我们需要一个接口、一个结构体、一个方法,以及它们之间应该如何相互关联等等。任何帮助都将不胜感激。提前感谢。

创建 UserService 和 EmailService 接口,

type UserService interface{
    GetUserForRequest(core.WebContext)(user, error)
}
type DefaultUserService struct{

}
func (userService *DefaultUserServer) GetUserForRequest(core.WebContext)(user, error){
   //一些代码
   return user{}, nil
}

在包中创建一个可赋值的 core.BuildAppContext 变量,然后通过 setter 设置它。或者将其转换为一个接口:AppBuilder,以演示如何使用函数来实现(我会使用接口和依赖注入器来实现):

package core
type BuildAppContext func(context.Context, Registry)
var defaultAppBuilder BuildAppContext

func SetAppBuilder(f BuildAppContext){
   defaultAppBuilder = f
}
func init(){
  defaultAppBuilder = func (context.Context, Registery){
        // 这里是一些代码
   }
}

测试

// 模拟 UserService
type MockUserService struct{

}
func (userService *typeMockUserService) GetUserForRequest(core.WebContext)(user, error){
   return user{id:1}, nil
}
func TestUserEmail(t *testing.T){
   userServer := UserServer{}
   userServer.UserService = &MockUserService{}
   //userServer.EmailService = &MockEmailService{} 未实现

   core.SetAppBuilder(func (context.Context, Registery){
       // 一些代码
   })
    e := echo.New()
    req := httptest.NewRequest(http.MethodGet, "/", nil)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    c.SetPath("/test/email_template/")
    c.SetParamNames("etpl_id")
    c.SetParamValues("12")

    if assert.NoError(t, userServer.EmailTemplateDelete(c)) {
            assert.Equal(t, http.StatusOK, rec.Code)
            //assert.Equal(t, userJSON, strings.TrimSpace(rec.Body.String()))
    }
}

更改你的处理程序,将其附加到承载所有依赖项和配置的 UserServer 上,另一种选择是使用全局变量但不推荐,更多细节请查阅谷歌。

type UserServer struct{
    UserService UserService
   //EmailService EmailService
   
}
func (userServer UserServer) EmailTemplate(c echo.Context) error {
   // 一些代码
 **//想要模拟这个函数**
    u, err := userServer.UserService.GetUserForRequest(wc)  
   // 一些代码
}

我分享上面的代码是为了提供一些实现的技巧,在开始实现之前,我强烈建议你查阅谷歌上关于 Go API 依赖注入的资料。

在Go中模拟其他包中的函数是可行的,主要通过接口和依赖注入实现。以下是针对你API测试的具体解决方案:

1. 创建接口和依赖注入

首先,为需要模拟的服务创建接口:

// service/mocks/service.go
package mocks

import (
    "context"
    "github.com/stretchr/testify/mock"
    "your-project/core"
)

type UserService interface {
    GetUserForRequest(wc *core.WebContext) (*model.User, error)
    DeleteEmailTemplate(ctx context.Context, templateUID, orgID string) error
}

// Mock实现
type MockUserService struct {
    mock.Mock
}

func (m *MockUserService) GetUserForRequest(wc *core.WebContext) (*model.User, error) {
    args := m.Called(wc)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    }
    return args.Get(0).(*model.User), args.Error(1)
}

func (m *MockUserService) DeleteEmailTemplate(ctx context.Context, templateUID, orgID string) error {
    args := m.Called(ctx, templateUID, orgID)
    return args.Error(0)
}

2. 重构API函数以接受依赖

// handler/email_template.go
package handler

import (
    "context"
    "net/http"
    "github.com/labstack/echo/v4"
    "your-project/core"
    "your-project/model"
    "your-project/service"
)

type EmailHandler struct {
    UserService service.UserService
    AppContextBuilder func(ctx context.Context, registry interface{}) *core.AppContext
}

func NewEmailHandler(userService service.UserService) *EmailHandler {
    return &EmailHandler{
        UserService: userService,
        AppContextBuilder: core.BuildAppContext, // 默认使用真实实现
    }
}

func (h *EmailHandler) EmailTemplateDelete(c echo.Context) error {
    wc := core.GetWebContext(c)
    
    eTplUID := wc.Param("etpl_id")
    if len(eTplUID) == 0 {
        e := errors.Wrap(nil, "email template uid is required")
        wc.Logger().Error(e)
        return wc.JSON(http.StatusBadRequest, model.NewErrorResponse().AddError(e))
    }

    // 使用注入的服务
    u, err := h.UserService.GetUserForRequest(wc)
    if err != nil {
        return wc.JSON(http.StatusUnauthorized, model.NewErrorResponse("error in fetching user details"))
    }

    // 使用注入的构建函数
    appCtx := h.AppContextBuilder(context.Background(), wc.Registry)

    err = h.UserService.DeleteEmailTemplate(appCtx, eTplUID, u.OrgID)
    if err != nil {
        return wc.JSON(http.StatusInternalServerError, model.NewErrorResponse().AddError(err))
    }
    
    return wc.JSON(http.StatusOK, model.NewSuccessResponse("Email template deleted successfully"))
}

3. 编写单元测试

// handler/email_template_test.go
package handler

import (
    "context"
    "net/http"
    "net/http/httptest"
    "testing"
    "github.com/labstack/echo/v4"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    "your-project/core"
    "your-project/model"
    "your-project/service/mocks"
)

func TestEmailTemplateDelete(t *testing.T) {
    // 创建mock服务
    mockUserService := new(mocks.MockUserService)
    
    // 设置期望
    mockUser := &model.User{
        ID:    "user-123",
        OrgID: "org-456",
    }
    
    mockUserService.On("GetUserForRequest", mock.AnythingOfType("*core.WebContext")).
        Return(mockUser, nil)
    
    mockUserService.On("DeleteEmailTemplate", 
        mock.AnythingOfType("*context.emptyCtx"),
        "12",
        "org-456").
        Return(nil)
    
    // 创建handler
    handler := NewEmailHandler(mockUserService)
    
    // 模拟BuildAppContext
    handler.AppContextBuilder = func(ctx context.Context, registry interface{}) *core.AppContext {
        return &core.AppContext{
            Context:  ctx,
            Registry: registry,
        }
    }
    
    // 创建测试环境
    e := echo.New()
    req := httptest.NewRequest(http.MethodDelete, "/test/email_template/12", nil)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    c.SetPath("/test/email_template/:etpl_id")
    c.SetParamNames("etpl_id")
    c.SetParamValues("12")
    
    // 执行测试
    err := handler.EmailTemplateDelete(c)
    
    // 验证
    assert.NoError(t, err)
    assert.Equal(t, http.StatusOK, rec.Code)
    
    // 验证mock调用
    mockUserService.AssertExpectations(t)
}

func TestEmailTemplateDelete_InvalidTemplateID(t *testing.T) {
    mockUserService := new(mocks.MockUserService)
    handler := NewEmailHandler(mockUserService)
    
    e := echo.New()
    req := httptest.NewRequest(http.MethodDelete, "/test/email_template/", nil)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    c.SetPath("/test/email_template/:etpl_id")
    c.SetParamNames("etpl_id")
    c.SetParamValues("") // 空模板ID
    
    err := handler.EmailTemplateDelete(c)
    
    assert.NoError(t, err)
    assert.Equal(t, http.StatusBadRequest, rec.Code)
    mockUserService.AssertNotCalled(t, "GetUserForRequest")
    mockUserService.AssertNotCalled(t, "DeleteEmailTemplate")
}

func TestEmailTemplateDelete_Unauthorized(t *testing.T) {
    mockUserService := new(mocks.MockUserService)
    handler := NewEmailHandler(mockUserService)
    
    // 模拟认证失败
    mockUserService.On("GetUserForRequest", mock.AnythingOfType("*core.WebContext")).
        Return(nil, errors.New("unauthorized"))
    
    e := echo.New()
    req := httptest.NewRequest(http.MethodDelete, "/test/email_template/12", nil)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    c.SetPath("/test/email_template/:etpl_id")
    c.SetParamNames("etpl_id")
    c.SetParamValues("12")
    
    err := handler.EmailTemplateDelete(c)
    
    assert.NoError(t, err)
    assert.Equal(t, http.StatusUnauthorized, rec.Code)
    mockUserService.AssertExpectations(t)
}

4. 使用gomock替代方案

如果你更喜欢使用gomock:

// 生成mock代码
// mockgen -source=service.go -destination=mocks/service_mock.go -package=mocks

// 在测试中使用
func TestEmailTemplateDelete_WithGoMock(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    
    mockUserService := mocks.NewMockUserService(ctrl)
    
    // 设置期望
    mockUserService.EXPECT().
        GetUserForRequest(gomock.Any()).
        Return(&model.User{OrgID: "org-456"}, nil)
    
    mockUserService.EXPECT().
        DeleteEmailTemplate(gomock.Any(), "12", "org-456").
        Return(nil)
    
    handler := NewEmailHandler(mockUserService)
    // ... 其余测试代码
}

这种方法通过依赖注入实现了对其他包中函数的模拟,使单元测试更加独立和可控。

回到顶部