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
非常感谢阿里! 😊
你好 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)
// ... 其余测试代码
}
这种方法通过依赖注入实现了对其他包中函数的模拟,使单元测试更加独立和可控。

