我试图在我的SignUp处理程序和对数据库的调用上实现单元测试。然而,它在我的SignUp处理程序中的数据库调用上抛出了panic错误。这是一个简单的SignUp处理程序,它接收带有用户名,密码和电子邮件的JSON。然后我将使用SELECT语句来检查此用户名是否在SignUp处理程序本身中重复。
当我把我的post请求发送到这个处理程序时,这一切都可以工作。然而,当我实际进行单元测试时,它不工作,并向我抛出了2条错误消息。我觉得这是因为数据库没有在测试环境中初始化,但我不知道如何在不使用第三方框架进行模拟数据库的情况下做到这一点。
错误信息
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
注册。去
package handler
type SignUpJson struct {
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
}
func SignUp(w http.ResponseWriter, r *http.Request) {
// Set Headers
w.Header().Set("Content-Type", "application/json")
var newUser auth_management.SignUpJson
// Reading the request body and UnMarshal the body to the LoginJson struct
bs, _ := io.ReadAll(req.Body)
if err := json.Unmarshal(bs, &newUser); err != nil {
utils.ResponseJson(w, http.StatusInternalServerError, "Internal Server Error")
log.Println("Internal Server Error in UnMarshal JSON body in SignUp route:", err)
return
}
ctx := context.Background()
ctx, cancel = context.WithTimeout(ctx, time.Minute * 2)
defer cancel()
// Check if username already exists in database (duplicates not allowed)
isExistingUsername := database.GetUsername(ctx, newUser.Username) // throws panic error here when testing
if isExistingUsername {
utils.ResponseJson(w, http.StatusBadRequest, "Username has already been taken. Please try again.")
return
}
// other code logic...
}
sqlquery.go
package database
var SQL_SELECT_FROM_USERS = "SELECT %s FROM users WHERE %s = $1;"
func GetUsername(ctx context.Context, username string) bool {
row := conn.QueryRow(ctx, fmt.Sprintf(SQL_SELECT_FROM_USERS, "username", "username"), username)
return row.Scan() != pgx.ErrNoRows
}
SignUp_test.go
package handler
func Test_SignUp(t *testing.T) {
var tests = []struct {
name string
postedData SignUpJson
expectedStatusCode int
}{
{
name: "valid login",
postedData: SignUpJson{
Username: "testusername",
Password: "testpassword",
Email: "test@email.com",
},
expectedStatusCode: 200,
},
}
for _, e := range tests {
jsonStr, err := json.Marshal(e.postedData)
if err != nil {
t.Fatal(err)
}
// Setting a request for testing
req, _ := http.NewRequest(http.MethodPost, "/signup", strings.NewReader(string(jsonStr)))
req.Header.Set("Content-Type", "application/json")
// Setting and recording the response
res := httptest.NewRecorder()
handler := http.HandlerFunc(SignUp)
handler.ServeHTTP(res, req)
if res.Code != e.expectedStatusCode {
t.Errorf("%s: returned wrong status code; expected %d but got %d", e.name, e.expectedStatusCode, res.Code)
}
}
}
setup_test.go
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
我在这里看到了一个类似的问题,但不确定这是否是正确的方法,因为没有回应,答案令人困惑:How to write an unit test for a handler that invokes a function that interacts with db in Golang using pgx driver?
1条答案
按热度按时间46scxncf1#
让我试着帮助你弄清楚如何实现这些事情。我对你的代码进行了一点重构,但总体思路和使用的工具仍然与你的相同。首先,我将分享分散在两个文件中的生产代码:
handlers/handlers.go
和repo/repo.go
。handlers/handlers.go
文件这里,存在两个主要差异:
1.使用的
context
。您不必示例化另一个ctx
,只需使用与http.Request
一起提供的ctx
。1.客户端使用的
sql
。正确的方法是通过context.Context
传递。对于这种情况,您不必构建任何结构或使用任何接口等。只需编写一个期望*sql.DB
作为参数的函数。请记住,函数是一等公民。"DB"
应该是一个常量,我们必须检查上下文值中是否存在这个条目,但为了简洁起见,我省略了这些检查。repo/repo.go
文件这里的代码与您的代码非常相似,除了这两个小东西:
1.当您希望考虑上下文时,有一个名为
QueryRowContext
的专用方法。1.当你需要建立一个SQL查询时,使用预准备语句特性。不要用
fmt.Sprintf
连接东西,原因有两个:安全性和可测试性。现在,我们来看看测试代码。
handlers/handlers_test.go
文件这里,与您的版本相比有很多变化。让我快速回顾一下:
httptest
包,它提供了构建和AssertHTTP请求和响应的内容。sqlmock
包。当涉及到模拟数据库时,它是事实上的标准。context
将sql
客户端与http.Request
并排传递。github.com/stretchr/testify/assert
包中完成。这同样适用于这里:有重构的空间(例如,您可以通过使用表驱动测试特性来返工测试)。
外接
这可以被认为是编写Go代码的惯用方法。我知道这可能非常具有挑战性,特别是在开始的时候。如果你需要某些部分的进一步细节,请告诉我,我很乐意帮助你,谢谢!