CreateUser增加单测
This commit is contained in:
207
internal/app/controller/user/user_controller_test.go
Normal file
207
internal/app/controller/user/user_controller_test.go
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package user_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/user"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockUserRepository 是 UserRepository 接口的模拟实现
|
||||||
|
type MockUserRepository struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 模拟 UserRepository 的 Create 方法
|
||||||
|
func (m *MockUserRepository) Create(user *models.User) error {
|
||||||
|
args := m.Called(user)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUsername 模拟 UserRepository 的 FindByUsername 方法
|
||||||
|
func (m *MockUserRepository) FindByUsername(username string) (*models.User, error) {
|
||||||
|
args := m.Called(username)
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
return args.Get(0).(*models.User), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByID 模拟 UserRepository 的 FindByID 方法
|
||||||
|
func (m *MockUserRepository) FindByID(id uint) (*models.User, error) {
|
||||||
|
args := m.Called(id)
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
return args.Get(0).(*models.User), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCreateUser 测试 CreateUser 方法
|
||||||
|
func TestCreateUser(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode) // 设置 Gin 为测试模式
|
||||||
|
|
||||||
|
// 创建一个不输出日志的真实 logs.Logger 实例
|
||||||
|
discardSyncer := zapcore.AddSync(io.Discard)
|
||||||
|
encoderConfig := zap.NewProductionEncoderConfig()
|
||||||
|
encoder := zapcore.NewConsoleEncoder(encoderConfig)
|
||||||
|
core := zapcore.NewCore(encoder, discardSyncer, zap.DebugLevel) // 设置为 DebugLevel 以确保所有日志都被处理(并丢弃)
|
||||||
|
zapLogger := zap.New(core)
|
||||||
|
sugaredLogger := zapLogger.Sugar()
|
||||||
|
silentLogger := &logs.Logger{SugaredLogger: sugaredLogger}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
requestBody user.CreateUserRequest
|
||||||
|
mockRepoSetup func(*MockUserRepository)
|
||||||
|
expectedResponse map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功创建用户",
|
||||||
|
requestBody: user.CreateUserRequest{
|
||||||
|
Username: "testuser",
|
||||||
|
Password: "password123",
|
||||||
|
},
|
||||||
|
mockRepoSetup: func(m *MockUserRepository) {
|
||||||
|
// 模拟 Create 成功
|
||||||
|
m.On("Create", mock.AnythingOfType("*models.User")).Return(nil).Once()
|
||||||
|
// 在成功创建用户的路径下,FindByUsername 不会被调用,因此这里不需要设置其期望
|
||||||
|
},
|
||||||
|
expectedResponse: map[string]interface{}{
|
||||||
|
"code": float64(http.StatusOK),
|
||||||
|
"message": "用户创建成功",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"username": "testuser",
|
||||||
|
"id": mock.Anything, // ID 是动态生成的,我们只检查存在
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "请求参数绑定失败_密码过短",
|
||||||
|
requestBody: user.CreateUserRequest{
|
||||||
|
Username: "testuser2",
|
||||||
|
Password: "123", // 密码少于6位
|
||||||
|
},
|
||||||
|
mockRepoSetup: func(m *MockUserRepository) {
|
||||||
|
// 不会调用 Create 或 FindByUsername
|
||||||
|
},
|
||||||
|
expectedResponse: map[string]interface{}{
|
||||||
|
"code": float64(http.StatusBadRequest),
|
||||||
|
"message": "Key: 'CreateUserRequest.Password' Error:Field validation for 'Password' failed on the 'min' tag",
|
||||||
|
"data": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "请求参数绑定失败_缺少用户名",
|
||||||
|
requestBody: user.CreateUserRequest{
|
||||||
|
Password: "password123",
|
||||||
|
},
|
||||||
|
mockRepoSetup: func(m *MockUserRepository) {
|
||||||
|
// 不会调用 Create 或 FindByUsername
|
||||||
|
},
|
||||||
|
expectedResponse: map[string]interface{}{
|
||||||
|
"code": float64(http.StatusBadRequest),
|
||||||
|
"message": "Key: 'CreateUserRequest.Username' Error:Field validation for 'Username' failed on the 'required' tag",
|
||||||
|
"data": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "用户名已存在",
|
||||||
|
requestBody: user.CreateUserRequest{
|
||||||
|
Username: "existinguser",
|
||||||
|
Password: "password123",
|
||||||
|
},
|
||||||
|
mockRepoSetup: func(m *MockUserRepository) {
|
||||||
|
// 模拟 Create 失败,因为用户名已存在
|
||||||
|
m.On("Create", mock.AnythingOfType("*models.User")).Return(errors.New("duplicate entry")).Once()
|
||||||
|
// 模拟 FindByUsername 找到用户,确认是用户名重复
|
||||||
|
m.On("FindByUsername", "existinguser").Return(&models.User{Username: "existinguser"}, nil).Once()
|
||||||
|
},
|
||||||
|
expectedResponse: map[string]interface{}{
|
||||||
|
"code": float64(http.StatusConflict),
|
||||||
|
"message": "用户名已存在",
|
||||||
|
"data": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "创建用户失败_通用数据库错误",
|
||||||
|
requestBody: user.CreateUserRequest{
|
||||||
|
Username: "db_error_user",
|
||||||
|
Password: "password123",
|
||||||
|
},
|
||||||
|
mockRepoSetup: func(m *MockUserRepository) {
|
||||||
|
// 模拟 Create 失败,通用数据库错误
|
||||||
|
m.On("Create", mock.AnythingOfType("*models.User")).Return(errors.New("database error")).Once()
|
||||||
|
// 模拟 FindByUsername 找不到用户,确认不是用户名重复
|
||||||
|
m.On("FindByUsername", "db_error_user").Return(nil, gorm.ErrRecordNotFound).Once()
|
||||||
|
},
|
||||||
|
expectedResponse: map[string]interface{}{
|
||||||
|
"code": float64(http.StatusInternalServerError),
|
||||||
|
"message": "创建用户失败",
|
||||||
|
"data": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 初始化 Gin 上下文和记录器
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
ctx, _ := gin.CreateTestContext(w)
|
||||||
|
ctx.Request = httptest.NewRequest(http.MethodPost, "/users", nil) // 初始请求,后续会替换 Body
|
||||||
|
|
||||||
|
// 设置请求体
|
||||||
|
jsonBody, _ := json.Marshal(tt.requestBody)
|
||||||
|
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(jsonBody))
|
||||||
|
ctx.Request.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// 创建 Mock UserRepository
|
||||||
|
mockRepo := new(MockUserRepository)
|
||||||
|
|
||||||
|
// 设置 Mock UserRepository 行为
|
||||||
|
tt.mockRepoSetup(mockRepo)
|
||||||
|
|
||||||
|
// 创建控制器实例,使用静默日志器
|
||||||
|
controller := user.NewController(mockRepo, silentLogger, nil) // tokenService 在 CreateUser 中未使用,设为 nil
|
||||||
|
|
||||||
|
// 调用被测试的方法
|
||||||
|
controller.CreateUser(ctx)
|
||||||
|
|
||||||
|
// 解析响应体
|
||||||
|
var responseBody map[string]interface{}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &responseBody)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 断言响应体中的 code 字段
|
||||||
|
assert.Equal(t, tt.expectedResponse["code"], responseBody["code"])
|
||||||
|
|
||||||
|
// 断言响应内容 (除了 code 字段)
|
||||||
|
// 对于成功的创建,ID 是动态的,需要特殊处理
|
||||||
|
if tt.expectedResponse["code"] == float64(http.StatusOK) {
|
||||||
|
assert.NotNil(t, responseBody["data"].(map[string]interface{})["id"])
|
||||||
|
// 移除 ID 字段以便进行通用断言
|
||||||
|
delete(responseBody["data"].(map[string]interface{}), "id")
|
||||||
|
delete(tt.expectedResponse["data"].(map[string]interface{}), "id")
|
||||||
|
}
|
||||||
|
// 移除 code 字段以便进行通用断言
|
||||||
|
delete(responseBody, "code")
|
||||||
|
delete(tt.expectedResponse, "code")
|
||||||
|
assert.Equal(t, tt.expectedResponse, responseBody)
|
||||||
|
|
||||||
|
// 验证 Mock 期望是否都已满足
|
||||||
|
mockRepo.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user