英文:
Unit test for post request is not working in Go
问题
我在Go中编写了一个简单的POST请求处理程序,它运行良好,但是当我运行单元测试时,它会抛出错误。
与数据库的连接:
var err error
server.DBConn, err = sql.Open("mysql", "root:root@tcp(localhost:8889)/infomatrix_project")
defer server.DBConn.Close()
http.HandleFunc("/reg_startup", post.RegStartup)
http.ListenAndServe(":9000", nil)
处理程序函数:
type Startup struct {
Name string `json:"name"`
Login string `json:"login"`
Password string `json:"password"`
Email string `json:"email"`
Description string `json:"description"`
Logo string `json:"logo"`
LowestInvestment int `json:"lowestInvestment"`
HighestInvestment int `json:"highestInvestment"`
Region string `json:"region"`
WebSite string `json:"website"`
Industry string `json:"industry"`
}
func RegStartup(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
}
//other.AccessSetter(w)
var query Startup
err := json.NewDecoder(r.Body).Decode(&query)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
stmt, err := server.DBConn.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")
}
测试函数:
func TestRegStartup(t *testing.T) {
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("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
return
}
expected := `"data entered successfully"`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
return
}
}
错误信息:
--- FAIL: TestRegStartup (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102534408]
goroutine 18 [running]:
testing.tRunner.func1.2({0x1025bfa40, 0x102723f80})
/usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1529 +0x384
panic({0x1025bfa40, 0x102723f80})
/usr/local/go/src/runtime/panic.go:884 +0x204
database/sql.(*DB).conn(0x1025a4c9c?, {0x1025f75b8?, 0x1400009c018?}, 0xe0?)
/usr/local/go/src/database/sql/sql.go:1282 +0x28
database/sql.(*DB).prepare(0x140000e8190?, {0x1025f75b8, 0x1400009c018}, {0x102549c1b, 0xb1}, 0x0?)
/usr/local/go/src/database/sql/sql.go:1587 +0x34
database/sql.(*DB).PrepareContext.func1(0xa0?)
/usr/local/go/src/database/sql/sql.go:1561 +0x48
database/sql.(*DB).retry(0x140000d5c28?, 0x140000d5be8)
/usr/local/go/src/database/sql/sql.go:1538 +0x4c
database/sql.(*DB).PrepareContext(0x140000e8168?, {0x1025f75b8?, 0x1400009c018?}, {0x102549c1b?, 0x140000d5c01?})
/usr/local/go/src/database/sql/sql.go:1560 +0x74
database/sql.(*DB).Prepare(0x140000e8140?, {0x102549c1b?, 0x140000baaa0?})
/usr/local/go/src/database/sql/sql.go:1577 +0x40
github.com/SayatAbdikul/rest_api_for_startup/postRequests.RegStartup({0x1025f73a0, 0x140000a8ac0}, 0x14000104000)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup.go:38 +0x1d4
net/http.HandlerFunc.ServeHTTP(...)
/usr/local/go/src/net/http/server.go:2122
github.com/SayatAbdikul/rest_api_for_startup/package_test_test.TestRegStartup(0x14000082d00)
/Users/sayat/Documents/GitHub/rest_api_for_startup/package_test/regStartup_test.go:37 +0x1c0
testing.tRunner(0x14000082d00, 0x1025f4db8)
/usr/local/go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1629 +0x368
FAIL github.com/SayatAbdikul/rest_api_for_startup/package_test 0.515s
--- FAIL: TestRegStartup (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102371168]
goroutine 18 [running]:
testing.tRunner.func1.2({0x102467a40, 0x1025cbfa0})
/usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1529 +0x384
panic({0x102467a40, 0x1025cbfa0})
/usr/local/go/src/runtime/panic.go:884 +0x204
database/sql.(*DB).conn(0x10244cc9c?, {0x10249f5b8?, 0x14000112018?}, 0x60?)
/usr/local/go/src/database/sql/sql.go:1282 +0x28
database/sql.(*DB).prepare(0x14000160190?, {0x10249f5b8, 0x14000112018}, {0x1023f1c8b, 0xb1}, 0x0?)
/usr/local/go/src/database/sql/sql.go:1587 +0x34
database/sql.(*DB).PrepareContext.func1(0x40?)
/usr/local/go/src/database/sql/sql.go:1561 +0x48
database/sql.(*DB).retry(0x1400014bc18?, 0x1400014bbd8)
/usr/local/go/src/database/sql/sql.go:1538 +0x4c
database/sql.(*DB).PrepareContext(0x14000160168?, {0x10249f5b8?, 0x14000112018?}, {0x1023f1c8b?, 0x1400014bc01?})
/usr/local/go/src/database/sql/sql.go:1560 +0x74
database/sql.(*DB).Prepare(0x14000160140?, {0x1023f1c8b?, 0x14000130b40?})
/usr/local/go/src/database/sql/sql.go:1577 +0x40
github.com/SayatAbdikul/rest_api_for_startup/postRequests.RegStartup({0x10249f3a0, 0x14000120ac0}, 0x1400017c000)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup.go:38 +0x1d4
net/http.HandlerFunc.ServeHTTP(...)
/usr/local/go/src/net/http/server.go:2122
github.com/SayatAbdikul/rest_api_for_startup/postRequests_test.TestRegStartup(0x1400011e9c0)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup_test.go:38 +0x1c0
testing.tRunner(0x1400011e9c0, 0x10249cdc0)
/usr/local/go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1629 +0x368
FAIL github.com/SayatAbdikul/rest_api_for_startup/postRequests 0.935s
FAIL
我尝试将与数据库的连接更改为本地连接(原来是通过IP连接到服务器),还尝试更改测试的位置,但错误信息没有改变。
英文:
I wrote simple post request handler in go that works fine, but when I run the unit test for it, it throws errors.
The connection to db:
var err error
server.DBConn, err = sql.Open("mysql", "root:root@tcp(localhost:8889)/infomatrix_project")
defer server.DBConn.Close()
http.HandleFunc("/reg_startup", post.RegStartup)
http.ListenAndServe(":9000", nil)
The handler function:
type Startup struct {
Name string `json:"name"`
Login string `json:"login"`
Password string `json:"password"`
Email string `json:"email"`
Description string `json:"description"`
Logo string `json:"logo"`
LowestInvestment int `json:"lowestInvestment"`
HighestInvestment int `json:"highestInvestment"`
Region string `json:"region"`
WebSite string `json:"website"`
Industry string `json:"industry"`
}
func RegStartup(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
}
//other.AccessSetter(w)
var query Startup
err := json.NewDecoder(r.Body).Decode(&query)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
stmt, err := server.DBConn.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")
}
The testing function:
func TestRegStartup(t *testing.T) {
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("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
return
}
expected := `"data entered successfully"`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
return
}
}
the errors:
--- FAIL: TestRegStartup (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102534408]
goroutine 18 [running]:
testing.tRunner.func1.2({0x1025bfa40, 0x102723f80})
/usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1529 +0x384
panic({0x1025bfa40, 0x102723f80})
/usr/local/go/src/runtime/panic.go:884 +0x204
database/sql.(*DB).conn(0x1025a4c9c?, {0x1025f75b8?, 0x1400009c018?}, 0xe0?)
/usr/local/go/src/database/sql/sql.go:1282 +0x28
database/sql.(*DB).prepare(0x140000e8190?, {0x1025f75b8, 0x1400009c018}, {0x102549c1b, 0xb1}, 0x0?)
/usr/local/go/src/database/sql/sql.go:1587 +0x34
database/sql.(*DB).PrepareContext.func1(0xa0?)
/usr/local/go/src/database/sql/sql.go:1561 +0x48
database/sql.(*DB).retry(0x140000d5c28?, 0x140000d5be8)
/usr/local/go/src/database/sql/sql.go:1538 +0x4c
database/sql.(*DB).PrepareContext(0x140000e8168?, {0x1025f75b8?, 0x1400009c018?}, {0x102549c1b?, 0x140000d5c01?})
/usr/local/go/src/database/sql/sql.go:1560 +0x74
database/sql.(*DB).Prepare(0x140000e8140?, {0x102549c1b?, 0x140000baaa0?})
/usr/local/go/src/database/sql/sql.go:1577 +0x40
github.com/SayatAbdikul/rest_api_for_startup/postRequests.RegStartup({0x1025f73a0, 0x140000a8ac0}, 0x14000104000)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup.go:38 +0x1d4
net/http.HandlerFunc.ServeHTTP(...)
/usr/local/go/src/net/http/server.go:2122
github.com/SayatAbdikul/rest_api_for_startup/package_test_test.TestRegStartup(0x14000082d00)
/Users/sayat/Documents/GitHub/rest_api_for_startup/package_test/regStartup_test.go:37 +0x1c0
testing.tRunner(0x14000082d00, 0x1025f4db8)
/usr/local/go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1629 +0x368
FAIL github.com/SayatAbdikul/rest_api_for_startup/package_test 0.515s
--- FAIL: TestRegStartup (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102371168]
goroutine 18 [running]:
testing.tRunner.func1.2({0x102467a40, 0x1025cbfa0})
/usr/local/go/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1529 +0x384
panic({0x102467a40, 0x1025cbfa0})
/usr/local/go/src/runtime/panic.go:884 +0x204
database/sql.(*DB).conn(0x10244cc9c?, {0x10249f5b8?, 0x14000112018?}, 0x60?)
/usr/local/go/src/database/sql/sql.go:1282 +0x28
database/sql.(*DB).prepare(0x14000160190?, {0x10249f5b8, 0x14000112018}, {0x1023f1c8b, 0xb1}, 0x0?)
/usr/local/go/src/database/sql/sql.go:1587 +0x34
database/sql.(*DB).PrepareContext.func1(0x40?)
/usr/local/go/src/database/sql/sql.go:1561 +0x48
database/sql.(*DB).retry(0x1400014bc18?, 0x1400014bbd8)
/usr/local/go/src/database/sql/sql.go:1538 +0x4c
database/sql.(*DB).PrepareContext(0x14000160168?, {0x10249f5b8?, 0x14000112018?}, {0x1023f1c8b?, 0x1400014bc01?})
/usr/local/go/src/database/sql/sql.go:1560 +0x74
database/sql.(*DB).Prepare(0x14000160140?, {0x1023f1c8b?, 0x14000130b40?})
/usr/local/go/src/database/sql/sql.go:1577 +0x40
github.com/SayatAbdikul/rest_api_for_startup/postRequests.RegStartup({0x10249f3a0, 0x14000120ac0}, 0x1400017c000)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup.go:38 +0x1d4
net/http.HandlerFunc.ServeHTTP(...)
/usr/local/go/src/net/http/server.go:2122
github.com/SayatAbdikul/rest_api_for_startup/postRequests_test.TestRegStartup(0x1400011e9c0)
/Users/sayat/Documents/GitHub/rest_api_for_startup/postRequests/regStartup_test.go:38 +0x1c0
testing.tRunner(0x1400011e9c0, 0x10249cdc0)
/usr/local/go/src/testing/testing.go:1576 +0x10c
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1629 +0x368
FAIL github.com/SayatAbdikul/rest_api_for_startup/postRequests 0.935s
FAIL
i tried to change the connection to db(connection was to server by ip and i changed it to localhost) and also tried to change the location of test. Nothing in error has changed.
答案1
得分: 1
让我分享一下我的解决方案以及关于编写和测试Go代码的一些见解。通常,我会按照以下方式进行,但可能还有更好的管理方式。
代码包含在两个文件中。让我们从生产代码开始。
handlers/handlers.go
文件
package handlers
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
)
type Startup struct {
Name string `json:"name"`
Password string `json:"password"`
Email string `json:"email"`
}
func RegStartup(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
}
//other.AccessSetter(w)
var query Startup
err := json.NewDecoder(r.Body).Decode(&query)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
dbConn := r.Context().Value("Db").(*sql.DB)
if dbConn == nil {
log.Fatal("database connection must be provided!")
return
}
stmt, err := dbConn.Prepare(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`)
defer stmt.Close()
if err != nil {
log.Fatal(err)
return
}
_, err = stmt.Exec(query.Name, query.Password, query.Email)
if err != nil {
log.Fatal(err)
return
}
fmt.Fprintf(w, "data entered successfully")
}
这里的代码与你的代码非常相似。我简化了Startup
结构体以处理较少的字段。最重要的是将*sql.DB
传递给处理程序函数。这是通过上下文完成的,这是实现它的最佳方式之一。在main.go
文件中,你可以自由地设置超时时间,在时间到期时取消每个正在进行的调用。
请记住,当从上下文中获取值时(还有值的类型!),始终检查
nil
值。
handlers/handlers_test.go
文件
package handlers
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestRegStartup(t *testing.T) {
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
defer db.Close()
if err != nil {
t.Errorf("err not expected while opening mock connection %v", err)
}
mock.ExpectPrepare(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`)
mock.ExpectExec(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`).WithArgs("john doe", "123456789", "john.doe@example.com").WillReturnResult(sqlmock.NewResult(1, 0))
w := httptest.NewRecorder()
r := &http.Request{
Header: make(http.Header),
}
r.Header.Set("Content-Type", "application/json")
r.Method = http.MethodPost
r.Body = io.NopCloser(strings.NewReader(`{"name": "john doe", "password": "123456789", "email": "john.doe@example.com"}`))
r = r.WithContext(context.WithValue(r.Context(), "Db", db))
RegStartup(w, r)
assert.Equal(t, "data entered successfully", w.Body.String())
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("not all expectations were met %v", err)
}
}
测试代码需要更多的解释:
- 你应该依赖
sqlmock
包来处理数据库的使用。你可以指定如何匹配查询/命令以及它们应该返回的结果。 - 你必须创建一个
http.ResponseWriter
和*http.Request
来调用RegStartup
函数。你可以轻松使用httptest
包。 - 确保设置HTTP请求的预期值。
- 将
*sql.DB
包添加到HTTP请求的上下文中。
如果你使用go test -v -cover
命令运行测试,一切都应该正常工作。
当然,还有很多可以改进的地方,但这一点可以作为实现更好的方式来编写HTTP处理程序和测试的良好起点。如果你需要更多细节或解释,请随时提问,谢谢!
英文:
let me share my solution with some insights on writing and testing Go code. Usually, I follow this way, but there could be better ways to manage it.
The code is contained in two files. Let's start with the production code.
handlers/handlers.go
file
package handlers
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
)
type Startup struct {
Name string `json:"name"`
Password string `json:"password"`
Email string `json:"email"`
}
func RegStartup(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
}
//other.AccessSetter(w)
var query Startup
err := json.NewDecoder(r.Body).Decode(&query)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
dbConn := r.Context().Value("Db").(*sql.DB)
if dbConn == nil {
log.Fatal("database connection must be provided!")
return
}
stmt, err := dbConn.Prepare(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`)
defer stmt.Close()
if err != nil {
log.Fatal(err)
return
}
_, err = stmt.Exec(query.Name, query.Password, query.Email)
if err != nil {
log.Fatal(err)
return
}
fmt.Fprintf(w, "data entered successfully")
}
Here, the code is pretty similar to yours. I simplified the Startup
struct to deal with fewer fields. The most important thing is passing the *sql.DB
to your handler function. This is done through the context which is one of the best ways to achieve it. In the main.go
file you're free to set a timeout on it to cancel each ongoing call when the time is expired.
> Keep in mind to always check for nil
value when you get from the context (and also the type of the value!).
handlers/handlers_test.go
file
package handlers
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestRegStartup(t *testing.T) {
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
defer db.Close()
if err != nil {
t.Errorf("err not expected while opening mock connection %v", err)
}
mock.ExpectPrepare(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`)
mock.ExpectExec(`INSERT INTO startups (name, password, email) VALUES (?, ?, ?)`).WithArgs("john doe", "123456789", "john.doe@example.com").WillReturnResult(sqlmock.NewResult(1, 0))
w := httptest.NewRecorder()
r := &http.Request{
Header: make(http.Header),
}
r.Header.Set("Content-Type", "application/json")
r.Method = http.MethodPost
r.Body = io.NopCloser(strings.NewReader(`{"name": "john doe", "password": "123456789", "email": "john.doe@example.com"}`))
r = r.WithContext(context.WithValue(r.Context(), "Db", db))
RegStartup(w, r)
assert.Equal(t, "data entered successfully", w.Body.String())
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("not all expectations were met %v", err)
}
}
The test code deserves more explanations:
- You should rely on the
sqlmock
package to deal with the database usage. You can specify how to match the queries/commands and which results they are supposed to return. - You have to create an
http.ResponseWriter
and*http.Request
to invoke theRegStartup
function. You can easily use thehttptest
package. - Make sure to set up the HTTP request as expected.
- Add to the context of the HTTP request the
*sql.DB
package.
If you run the test with the command go test -v -cover
everything should work as expected.
For sure, there are a lot of things to improve but this point could be a good starting point to implement HTTP handlers and tests in a better way. Feel free to ask if you need more details or clarifications, thanks!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论