Golang 使用处理程序创建模拟数据库并使用接口调用数据库
我正在尝试对我的注册处理程序和数据库调用实施单元测试。但是,它在我的注册处理程序中的数据库调用上引发紧急错误。它是一个简单的注册处理程序,用于接收包含用户名、密码和电子邮件的 json。然后,我将使用 select 语句来检查该用户名是否在 signup 处理程序本身内重复。
当我向该处理程序发送我的发布请求时,这一切都有效。然而,当我实际进行单元测试时,它不起作用并给我抛出了两条错误消息。我觉得这是因为数据库没有在测试环境中初始化,但我不知道如何在不使用第三方框架进行模拟数据库的情况下做到这一点。
错误消息
panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference
signup.go
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: "[email protected]", }, 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 invokes a function that intersted with db in golang using pgx 驱动程序?
正确答案
让我尝试帮助您弄清楚如何实现这些目标。我对你的代码进行了一些重构,但总体思路和使用的工具仍然与你的相同。首先,我将分享分为两个文件的生产代码:handlers/handlers.go
和 repo/repo.go
。
handlers/handlers.go
文件
package handlers import ( "context" "database/sql" "encoding/json" "io" "net/http" "time" "handlertest/repo" ) type signupjson struct { username string `json:"username"` password string `json:"password"` email string `json:"email"` } func signup(w http.responsewriter, r *http.request) { w.header().set("content-type", "application/json") var newuser signupjson bs, _ := io.readall(r.body) if err := json.unmarshal(bs, &newuser); err != nil { w.writeheader(http.statusbadrequest) w.write([]byte(err.error())) return } ctx, cancel := context.withtimeout(r.context(), time.minute*2) defer cancel() db, _ := ctx.value("db").(*sql.db) if isexistingusername := repo.getusername(ctx, db, newuser.username); isexistingusername { w.writeheader(http.statusbadrequest) w.write([]byte("username already present")) return } w.writeheader(http.statusok) }
这里有两个主要区别:
- 使用的
context
。您不必实例化另一个ctx
,只需使用与http.request
一起提供的那个即可。 - 使用的
sql
客户端。正确的方法是通过context.context
来传递。对于这种情况,您不必构建任何结构或使用任何接口等。只需编写一个需要*sql.db
作为参数的函数即可。请记住这一点,函数是一等公民。
当然,还有重构的空间。 "db"
应该是一个常量,我们必须检查上下文值中是否存在此条目,但为了简洁起见,我省略了这些检查。
repo/repo.go
文件
package repo import ( "context" "database/sql" "github.com/jackc/pgx/v5" ) func getusername(ctx context.context, db *sql.db, username string) bool { row := db.queryrowcontext(ctx, "select username from users where username = $1", username) return row.scan() != pgx.errnorows }
这里的代码与您的非常相似,除了以下两个小问题:
- 当您希望考虑上下文时,有一个名为
queryrowcontext
的专用方法。 - 当您必须构建 sql 查询时,请使用准备好的语句功能。不要将内容与
fmt.sprintf
连接起来,原因有两个:安全性和可测试性。
现在,我们要看看测试代码。
handlers/handlers_test.go
文件
package handlers import ( "context" "net/http" "net/http/httptest" "strings" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/jackc/pgx/v5" "github.com/stretchr/testify/assert" ) func TestSignUp(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) if err != nil { t.Fatalf("err not expected while open a mock db, %v", err) } defer db.Close() t.Run("NewUser", func(t *testing.T) { mock.ExpectQuery("SELECT username FROM users WHERE username = $1").WithArgs("<a href="https://www.php.cn/link/89fee0513b6668e555959f5dc23238e9" class="__cf_email__" data-cfemail="ec86838482c2888389ac89948d819c8089c28f8381">[email protected]</a>").WillReturnError(pgx.ErrNoRows) w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodPost, "/signup", strings.NewReader(`{"username": "<a href="https://www.php.cn/link/89fee0513b6668e555959f5dc23238e9" class="__cf_email__" data-cfemail="3c56535452125853597c59445d514c5059125f5351">[email protected]</a>", "password": "1234", "email": "<a href="https://www.php.cn/link/89fee0513b6668e555959f5dc23238e9" class="__cf_email__" data-cfemail="670d080f094903080227021f060a170b024904080a">[email protected]</a>"}`)) ctx := context.WithValue(r.Context(), "DB", db) r = r.WithContext(ctx) SignUp(w, r) assert.Equal(t, http.StatusOK, w.Code) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("not all expectations were met: %v", err) } }) t.Run("AlreadyExistentUser", func(t *testing.T) { rows := sqlmock.NewRows([]string{"username"}).AddRow("<a href="https://www.php.cn/link/89fee0513b6668e555959f5dc23238e9" class="__cf_email__" data-cfemail="9df7f2f5f3b3f9f2f8ddf8e5fcf0edf1f8b3fef2f0">[email protected]</a>") mock.ExpectQuery("SELECT username FROM users WHERE username = $1").WithArgs("<a href="https://www.php.cn/link/89fee0513b6668e555959f5dc23238e9" class="__cf_email__" data-cfemail="107a7f787e3e747f75507568717d607c753e737f7d">[email protected]</a>").WillReturnRows(rows) w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodPost, "/signup", strings.NewReader(`{"username": "<a href="https://www.php.cn/link/89fee0513b6668e555959f5dc23238e9" class="__cf_email__" data-cfemail="33595c5b5d1d575c5673564b525e435f561d505c5e">[email protected]</a>", "password": "1234", "email": "<a href="https://www.php.cn/link/89fee0513b6668e555959f5dc23238e9" class="__cf_email__" data-cfemail="a3c9cccbcd8dc7ccc6e3c6dbc2ced3cfc68dc0ccce">[email protected]</a>"}`)) ctx := context.WithValue(r.Context(), "DB", db) r = r.WithContext(ctx) SignUp(w, r) assert.Equal(t, http.StatusBadRequest, w.Code) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("not all expectations were met: %v", err) } }) }
这里,与您的版本相比有很多变化。让我快速回顾一下:
- 使用子测试功能为测试提供层次结构。
- 使用
httptest
包,它提供了用于构建和断言 http 请求和响应的内容。 - 使用
sqlmock
包。模拟数据库的事实上的标准。 - 使用
context
传递sql
客户端以及http.request
。 - 已使用
github.com/stretchr/testify/assert
包完成断言。
这同样适用于这里:有重构的空间(例如,您可以使用表驱动测试功能重新设计测试)。
片尾
这可以被认为是编写 go 代码的惯用方式。我知道这可能非常具有挑战性,尤其是在一开始。如果您需要有关某些部分的更多详细信息,请告诉我,我将很乐意为您提供帮助,谢谢!
以上是Golang 使用处理程序创建模拟数据库并使用接口调用数据库的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

OpenSSL,作为广泛应用于安全通信的开源库,提供了加密算法、密钥和证书管理等功能。然而,其历史版本中存在一些已知安全漏洞,其中一些危害极大。本文将重点介绍Debian系统中OpenSSL的常见漏洞及应对措施。DebianOpenSSL已知漏洞:OpenSSL曾出现过多个严重漏洞,例如:心脏出血漏洞(CVE-2014-0160):该漏洞影响OpenSSL1.0.1至1.0.1f以及1.0.2至1.0.2beta版本。攻击者可利用此漏洞未经授权读取服务器上的敏感信息,包括加密密钥等。

Go语言中用于浮点数运算的库介绍在Go语言(也称为Golang)中,进行浮点数的加减乘除运算时,如何确保精度是�...

Go爬虫Colly中的Queue线程问题探讨在使用Go语言的Colly爬虫库时,开发者常常会遇到关于线程和请求队列的问题。�...

后端学习路径:从前端转型到后端的探索之旅作为一名从前端开发转型的后端初学者,你已经有了nodejs的基础,...

本文介绍在Debian系统下监控PostgreSQL数据库的多种方法和工具,助您全面掌握数据库性能监控。一、利用PostgreSQL内置监控视图PostgreSQL自身提供多个视图用于监控数据库活动:pg_stat_activity:实时展现数据库活动,包括连接、查询和事务等信息。pg_stat_replication:监控复制状态,尤其适用于流复制集群。pg_stat_database:提供数据库统计信息,例如数据库大小、事务提交/回滚次数等关键指标。二、借助日志分析工具pgBadg

Go语言中字符串打印的区别:使用Println与string()函数的效果差异在Go...

在BeegoORM框架下,如何指定模型关联的数据库?许多Beego项目需要同时操作多个数据库。当使用Beego...

Go语言中使用RedisStream实现消息队列时类型转换问题在使用Go语言与Redis...
