Go测试失败的数据库查询(空指针),但在Postman/Curl中正常工作。

huangapple go评论156阅读模式
英文:

Go test fails db query (nil pointer) but works with Postman/Curl

问题

我正在测试一个名为GetCats()的函数,它从MySQL数据库中获取所有的'cats'。当我通过Postman访问该端点时,由于使用了'COALESCE'函数,将字段设置为空字符串而不是null,所以没有出现空指针错误。

然而,当我测试这个函数时,出现了空指针错误,并且程序发生了恐慌。

  • panic: runtime error: invalid memory address or nil pointer dereference [recovered]

_test.go

func TestGetCats(t *testing.T) {
	// 创建到/cats端点的新请求,nil => body io.Reader
	req, err := http.NewRequest("GET", "/cats", nil)
	if err != nil {
		t.Fatal(err)
	}
	// 将从/cats端点接收到的响应存储到ResponseRecorder结构的指针中
	recorder := httptest.NewRecorder()

	handler := http.HandlerFunc(handlers.GetCats)

	// ----(在这里失败) 使用recorder和request访问端点 (在这里失败) ----//
	handler.ServeHTTP(recorder, req)

    // ------------------------------------------------------------------------//

	// 测试recorder的状态码
	if recorder.Code != http.StatusOK {
		t.Errorf("getCats返回了错误的状态码: got %v but want %v", recorder.Code, http.StatusOK)
	}
}

cathandlers.go

func GetCats(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	var animals []Animal

	// ---------------程序在这个查询上发生恐慌 --------------------//
	// 不能将null值输入到结构体中 => 返回空字符串代替
	
	res, err := Db.Query("SELECT id, name, 
                            COALESCE(age, '') as age, 
                            COALESCE(color, '') as color,
                            COALESCE(gender, '') as gender, 
                            COALESCE(breed, '') as breed, 
                            COALESCE(weight, '') as weight FROM cats")

	//------------------------------------------------//

非常感谢您的帮助!

额外说明:

  • 数据库中的'name'字段不为null,不需要在该字段上使用coalesce函数
  • 数据库中有一些空字段(故意的)
  • 只是对在Postman中的查询正常工作,但在从_test.go内部调用时出现问题感到困惑。
英文:

I'm testing GetCats() which is a function that gets all 'cats' from a mysql database. When I hit the endpoint through postman, there are no nil pointer errors due to 'COALESCE' which sets the field to an empty string if null.

However, when I test the function, there is a nil pointer error and the program panics out.

  • panic: runtime error: invalid memory address or nil pointer dereference [recovered]

_test.go

func TestGetCats(t *testing.T) {
	// create new request to /cats endpoint, nil => body io.Reader
	req, err := http.NewRequest("GET", "/cats", nil)
	if err != nil {
		t.Fatal(err)
	}
	// Will store response received from /cats endpoint => pointer to ResonseRecorder struct
	recorder := httptest.NewRecorder()

	handler := http.HandlerFunc(handlers.GetCats)

	// ----(FAILS HERE) hit endpoint with recorder and request (FAILS HERE) ----//
	handler.ServeHTTP(recorder, req)

    // ------------------------------------------------------------------------//

	// test recorder status code
	if recorder.Code != http.StatusOK {
		t.Errorf("getCats return wrong status code: got %v but want %v", recorder.Code, http.StatusOK)
	}

cathandlers.go

func GetCats(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	var animals []Animal

	// ---------------Program panics on this query --------------------//
	// cannot input null values into struct => return empty string instead
	
	res, err := Db.Query("SELECT id, name, 
                            COALESCE(age, '') as age, 
                            COALESCE(color, '') as color,
                            COALESCE(gender, '') as gender, 
                            COALESCE(breed, '') as breed, 
                            COALESCE(weight, '') as weight FROM cats")

	//------------------------------------------------//

Any help would be greatly appreciated!

extra notes:

  • 'name' is not null in database, no need for coalesce on that field
  • there are a few null fields in the database (intentional)
  • just confused as to how the query works in postman but not while calling internally from _test.go

答案1

得分: 1

可能测试中的Db对象没有正确初始化。你最好定义一个结构体,并将DB作为依赖注入,并在测试中使用一个虚假的DB对象。例如,

// handlers.go
import (
        "database/sql"
        "net/http"
)

type App struct {
        DB *sql.DB
}

func (a *App) GetCats(w http.ResponseWriter, r *http.Request) {
        // var animals []Animal

        res, err := a.DB.Query("SELECT id, name, 
                            COALESCE(age, '') as age, 
                            COALESCE(color, '') as color,
                            COALESCE(gender, '') as gender, 
                            COALESCE(breed, '') as breed, 
                            COALESCE(weight, '') as weight FROM cats")

      // ....
}

// handlers_test.go
import (
        "net/http"
        "net/http/httptest"
        "testing"

        "github.com/DATA-DOG/go-sqlmock"
)

func TestGetCats(t *testing.T) {
        db, _, err := sqlmock.New()
        if err != nil {
                t.Fatalf("an error %s was not expected when openning a stub database connection", err)
        }

        app := &App{
                DB: db,
        }

        req, err := http.NewRequest("GET", "/cats", nil)
        if err != nil {
                t.Fatal(err)
        }
        // Will store response received from /cats endpoint => pointer to ResonseRecorder struct
        recorder := httptest.NewRecorder()

        // DB expected actions...

        handler := http.HandlerFunc(app.GetCats)
        handler.ServeHTTP(recorder, req)

        if recorder.Code != http.StatusOK {
                t.Errorf("getCats return wrong status code: got %v but want %v", recorder.Code, http.StatusOK)
        }
}

英文:

Probably the Db object is not initialized properly in the test. You'd better define a struct and inject DB as dependency and use a fake DB object in your test. For example,

// handlers.go
import (
        "database/sql"
        "net/http"
)

type App struct {
        DB *sql.DB
}

func (a *App) GetCats(w http.ResponseWriter, r *http.Request) {
        // var animals []Animal

        res, err := a.DB.Query("SELECT id, name, 
                            COALESCE(age, '') as age, 
                            COALESCE(color, '') as color,
                            COALESCE(gender, '') as gender, 
                            COALESCE(breed, '') as breed, 
                            COALESCE(weight, '') as weight FROM cats")

      // ....
}

// handlers_test.go
import (
        "net/http"
        "net/http/httptest"
        "testing"

        "github.com/DATA-DOG/go-sqlmock"
)

func TestGetCats(t *testing.T) {
        db, _, err := sqlmock.New()
        if err != nil {
                t.Fatalf("an error %s was not expected when openning a stub database connection", err)
        }

        app := &App{
                DB: db,
        }

        req, err := http.NewRequest("GET", "/cats", nil)
        if err != nil {
                t.Fatal(err)
        }
        // Will store response received from /cats endpoint => pointer to ResonseRecorder struct
        recorder := httptest.NewRecorder()

        // DB expected actions...

        handler := http.HandlerFunc(app.GetCats)
        handler.ServeHTTP(recorder, req)

        if recorder.Code != http.StatusOK {
                t.Errorf("getCats return wrong status code: got %v but want %v", recorder.Code, http.StatusOK)
        }
}

huangapple
  • 本文由 发表于 2021年11月25日 11:49:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/70105594.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定