You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 06:32:15