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