Go语言POST请求接口单元测试空指针报错求助
解决Go接口单元测试空指针panic问题
问题根源
单元测试直接调用RegStartup函数时,测试环境未初始化全局变量server.DBConn,导致执行server.DBConn.Prepare时触发空指针引用panic。正常运行时因为启动代码里完成了数据库连接初始化,所以功能正常。
解决方案
方案一:初始化测试专用数据库连接(集成测试)
在测试函数开头添加数据库初始化逻辑,使用独立的测试数据库避免污染生产数据:
func TestRegStartup(t *testing.T) { // 初始化测试数据库连接 var err error server.DBConn, err = sql.Open("mysql", "root:root@tcp(localhost:8889)/infomatrix_project_test") if err != nil { t.Fatalf("测试数据库连接失败: %v", err) } defer server.DBConn.Close() // 验证连接有效性 if err := server.DBConn.Ping(); err != nil { t.Fatalf("测试数据库ping失败: %v", err) } // 原有测试逻辑 load := Startup{"название на кириллице", "test_startup", "test_password", "test@example.com", "This is a test startup.", "https://example.com/test_startup_logo.jpg", 1000, 10000, "Kazakhstan", "https://teststartup.com", "IT"} loadBytes, _ := json.Marshal(load) request, err := http.NewRequest("POST", "/reg_startup", bytes.NewBuffer(loadBytes)) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() handler := http.HandlerFunc(postRequests.RegStartup) handler.ServeHTTP(rr, request) // 修正状态码断言 status := rr.Code if status != http.StatusOK { t.Errorf("处理函数返回错误状态码: 得到 %v 期望 %v", status, http.StatusOK) return } // 修正返回体断言(原接口返回无引号字符串) expected := "data entered successfully" if rr.Body.String() != expected { t.Errorf("处理函数返回错误内容: 得到 %q 期望 %q", rr.Body.String(), expected) return } }
注意事项:
- 提前创建独立的测试数据库
infomatrix_project_test,结构与生产库一致 - 测试后建议清理插入的测试数据,避免影响后续测试
方案二:Mock数据库连接(纯单元测试)
通过抽象数据库接口,使用mock对象模拟数据库行为,彻底脱离真实数据库依赖:
1. 重构业务代码,抽象DB接口
// postRequests包中定义数据库接口 type DBInterface interface { Prepare(query string) (*sql.Stmt, error) } // 修改RegStartup函数,通过参数注入DB连接 func RegStartup(db DBInterface, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") if r.Method != "POST" { fmt.Fprintf(w, "error: the request is not a POST type") return } var query Startup err := json.NewDecoder(r.Body).Decode(&query) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } stmt, err := db.Prepare("INSERT INTO startups (name, login, password, email, " + "description, logo, lowest_investment, highest_investment, region, website, industry) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") defer stmt.Close() if err != nil { log.Fatal(err) return } _, err = stmt.Exec(query.Name, query.Login, query.Password, query.Email, query.Description, query.Logo, query.LowestInvestment, query.HighestInvestment, query.Region, query.WebSite, query.Industry) if err != nil { log.Fatal(err) return } fmt.Fprintf(w, "data entered successfully") }
2. 正常启动时传入真实DB连接
http.HandleFunc("/reg_startup", func(w http.ResponseWriter, r *http.Request) { post.RegStartup(server.DBConn, w, r) })
3. 测试时使用Mock对象
import ( "github.com/stretchr/testify/mock" ) // MockDB 实现DBInterface接口 type MockDB struct { mock.Mock } func (m *MockDB) Prepare(query string) (*sql.Stmt, error) { args := m.Called(query) return args.Get(0).(*sql.Stmt), args.Error(1) } // MockStmt 模拟SQL语句对象 type MockStmt struct { mock.Mock } func (m *MockStmt) Close() error { args := m.Called() return args.Error(0) } func (m *MockStmt) Exec(args ...interface{}) (sql.Result, error) { callArgs := m.Called(args...) return callArgs.Get(0).(sql.Result), callArgs.Error(1) } func TestRegStartup(t *testing.T) { // 创建Mock对象并设置预期行为 mockStmt := new(MockStmt) mockStmt.On("Exec", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) mockStmt.On("Close").Return(nil) mockDB := new(MockDB) mockDB.On("Prepare", mock.AnythingOfType("string")).Return(mockStmt, nil) // 构造测试请求 load := Startup{"название на кириллице", "test_startup", "test_password", "test@example.com", "This is a test startup.", "https://example.com/test_startup_logo.jpg", 1000, 10000, "Kazakhstan", "https://teststartup.com", "IT"} loadBytes, _ := json.Marshal(load) request, err := http.NewRequest("POST", "/reg_startup", bytes.NewBuffer(loadBytes)) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() // 调用处理函数并传入MockDB postRequests.RegStartup(mockDB, rr, request) // 断言返回结果 status := rr.Code if status != http.StatusOK { t.Errorf("处理函数返回错误状态码: 得到 %v 期望 %v", status, http.StatusOK) return } expected := "data entered successfully" if rr.Body.String() != expected { t.Errorf("处理函数返回错误内容: 得到 %q 期望 %q", rr.Body.String(), expected) return } // 验证Mock方法是否被正确调用 mockDB.AssertExpectations(t) mockStmt.AssertExpectations(t) }
内容的提问来源于stack exchange,提问作者kto_to
相关产品推荐
相关产品推荐

