如何使用 Gorm、mux、postgresql 进行单元测试
我是 Go 和单元测试的新手。我使用 Go 与 Gorm、mux 和 postgresql 构建了一个名为“urlshortener”的小型项目。
搜索了很多文章后,有一个问题让我很烦恼。
为了使问题更清晰,我删除了一些不相关的代码,例如 connect db、.env 等。
我的代码如下(main.go):
package main
type Url struct {
ID uint `gorm:"primaryKey"` // used for shortUrl index
Url string `gorm:"unique"` // prevent duplicate url
ExpireAt string
ShortUrl string
}
var db *gorm.DB
var err error
func main() {
// gain access to database by getting .env
...
// database connection string
...
// make migrations to the dbif they have not already been created
db.AutoMigrate(&Url{})
// API routes
router := mux.NewRouter()
router.HandleFunc("/{id}", getURL).Methods("GET")
router.HandleFunc("/api/v1/urls", createURL).Methods("POST")
router.HandleFunc("/create/urls", createURLs).Methods("POST")
// Listener
http.ListenAndServe(":80", router)
// close connection to db when main func finishes
defer db.Close()
}
现在我正在为 getURL 函数构建单元测试,这是一个从我的数据库获取数据的 GET 方法。 postgresql 数据库名为 urlshortener,表名为 urls。
这是 getURL 函数代码:
func getURL(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var url Url
err := db.Find(&url, params["id"]).Error
if err != nil {
w.WriteHeader(http.StatusNotFound)
} else {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(url.Url)
}
}
我知道单元测试不是针对模拟数据的,它的目的是测试一个函数/方法是否稳定。虽然我导入了 mux 和 net/http 进行连接,但我认为对其进行的单元测试应该是“SQL 语法”。因此,我决定重点测试 gorm 是否向测试函数返回正确的值。
在这种情况下,db.Find 将返回一个 *gorm.DB 结构,该结构应与第二行完全相同。 (请参阅文档https://gorm.io/docs/query.html)
db.Find(&url, params["id"])
SELECT * FROM urls WHICH id=<input_number>
我的问题是在这种情况下(gorm+mux)如何编写单元测试来检查 SQL 语法是否正确?我查过一些文章,但大多数都在测试 http 连接状态,但没有测试 SQL。
而我的函数没有返回值,或者我需要重写该函数以具有返回值才能测试它?
下面是我心中的测试结构:
func TestGetURL(t *testing.T) {
//set const answer for this test
//set up the mock sql connection
//call getURL()
//check if equal with answer using assert
}
更新
立即 回答我有一个 testGetURL 的原型。现在我对此有了新的疑问。
func TestGetURL(t *testing.T) {
//set const answer for this test
testQuery := `SELECT * FROM "urls" WHERE id=1`
id := 1
//set up the mock sql connection
testDB, mock, err := sqlmock.New()
if err != nil {
panic("sqlmock.New() occurs an error")
}
// uses "gorm.io/driver/postgres" library
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: testDB,
PreferSimpleProtocol: true,
})
db, err = gorm.Open(dialector, &gorm.Config{})
if err != nil {
panic("Cannot open stub database")
}
//mock the db.Find function
rows := sqlmock.NewRows([]string{"id", "url", "expire_at", "short_url"}).
AddRow(1, "http://somelongurl.com", "some_date", "http://shorturl.com")
mock.ExpectQuery(regexp.QuoteMeta(testQuery)).
WillReturnRows(rows).WithArgs(id)
//create response writer and request for testing
mockedRequest, _ := http.NewRequest("GET", "/1", nil)
mockedWriter := httptest.NewRecorder()
//call getURL()
getURL(mockedWriter, mockedRequest)
//check values in mockedWriter using assert
}
在代码中,我模拟请求并使用 http、httptest 库进行响应。 我运行测试,但似乎 main.go 中的 getURL 函数无法接收我传入的参数,请参见下图。
当db.find
调用时,mock.ExpectQuery
接收它并开始比较它,到目前为止一切顺利。
db.Find(&url, params["id"])
mock.ExpectQuery(regexp.QuoteMeta(testQuery)).WillReturnRows(rows).WithArgs(id)
根据测试日志,它表明当db.Find
触发时,它只执行SELECT * FROM "urls"
,而不是我期望的SELECT * FROM "urls" " 其中“urls”.“id”= $1
。
但是当我用 postman 在本地测试 db.Find 并记录 SQL 语法时,它可以正确执行。见下图。
总之,我认为问题是我在getURL(mockedWriter,mockedRequest)
中放入的responeWriter/request是错误的,它导致getURL(w http.ResponseWriter, r *http.Request)
无法按我们的预期工作。
如果我遗漏了什么,请告诉我〜
任何重写代码的想法或方法都会有所帮助,谢谢!
I'm new in Go and unit test. I build a samll side projecy called "urlshortener" using Go with Gorm, mux and postgresql.
There is a qeustion annoying me after search many articles.
To make the question clean, I delete some irrelevant code like connect db, .env, etc
My code is below(main.go):
package main
type Url struct {
ID uint `gorm:"primaryKey"` // used for shortUrl index
Url string `gorm:"unique"` // prevent duplicate url
ExpireAt string
ShortUrl string
}
var db *gorm.DB
var err error
func main() {
// gain access to database by getting .env
...
// database connection string
...
// make migrations to the dbif they have not already been created
db.AutoMigrate(&Url{})
// API routes
router := mux.NewRouter()
router.HandleFunc("/{id}", getURL).Methods("GET")
router.HandleFunc("/api/v1/urls", createURL).Methods("POST")
router.HandleFunc("/create/urls", createURLs).Methods("POST")
// Listener
http.ListenAndServe(":80", router)
// close connection to db when main func finishes
defer db.Close()
}
Now I'm building unit test for getURL function, which is a GET method to get data from my postgresql database called urlshortener and the table name is urls.
Here is getURL function code:
func getURL(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var url Url
err := db.Find(&url, params["id"]).Error
if err != nil {
w.WriteHeader(http.StatusNotFound)
} else {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(url.Url)
}
}
This is work fine with my database. See curl command below:
I know that the unit test is not for mock data, and it aim to test a function/method is stable or not. Although I import mux and net/http for conncetion, but I think the unit test on it should be "SQL syntax". So I decide to focus on testing if gorm return the right value to the test function.
In this case, db.Find will return a *gorm.DB struct which should be exactly same with second line. (see docs https://gorm.io/docs/query.html)
db.Find(&url, params["id"])
SELECT * FROM urls WHICH id=<input_number>
My question is how to write a unit test on it for check the SQL syntax is correct or not in this case (gorm+mux)? I've check some articles, but most of them are testing the http connect status but not for SQL.
And my function do not have the return value, or I need to rewrite the function to have a return value before I can test it?
below is the test structure in my mind:
func TestGetURL(t *testing.T) {
//set const answer for this test
//set up the mock sql connection
//call getURL()
//check if equal with answer using assert
}
Update
According to @Emin Laletovic answer
Now I have a prototype of my testGetURL. Now I have new questions on it.
func TestGetURL(t *testing.T) {
//set const answer for this test
testQuery := `SELECT * FROM "urls" WHERE id=1`
id := 1
//set up the mock sql connection
testDB, mock, err := sqlmock.New()
if err != nil {
panic("sqlmock.New() occurs an error")
}
// uses "gorm.io/driver/postgres" library
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: testDB,
PreferSimpleProtocol: true,
})
db, err = gorm.Open(dialector, &gorm.Config{})
if err != nil {
panic("Cannot open stub database")
}
//mock the db.Find function
rows := sqlmock.NewRows([]string{"id", "url", "expire_at", "short_url"}).
AddRow(1, "http://somelongurl.com", "some_date", "http://shorturl.com")
mock.ExpectQuery(regexp.QuoteMeta(testQuery)).
WillReturnRows(rows).WithArgs(id)
//create response writer and request for testing
mockedRequest, _ := http.NewRequest("GET", "/1", nil)
mockedWriter := httptest.NewRecorder()
//call getURL()
getURL(mockedWriter, mockedRequest)
//check values in mockedWriter using assert
}
In the code, I mock the request and respone with http, httptest libs.
I run the test, but it seems that the getURL function in main.go cannot receive the args I pass in, see the pic below.
when db.find
called, mock.ExpectQuery
receive it and start to compare it, so far so good.
db.Find(&url, params["id"])
mock.ExpectQuery(regexp.QuoteMeta(testQuery)).WillReturnRows(rows).WithArgs(id)
According to the testing log, it shows that when db.Find
triggerd, it only excute SELECT * FROM "urls"
but not I expected SELECT * FROM "urls" WHERE "urls"."id" = $1
.
But when I test db.Find
on local with postman and log the SQL syntax out, it can be excute properly. see pic below.
In summary, I think the problem is the responeWriter/request I put in getURL(mockedWriter, mockedRequest)
are wrong, and it leads that getURL(w http.ResponseWriter, r *http.Request)
cannot work as we expect.
Please let me know if I missing anything~
Any idea or way to rewrite the code would be help, thank you!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
如果您只想测试
db.Find
返回的 SQL 字符串,则可以使用DryRun
功能(根据 文档)。但是,要为
getURL
函数编写测试,您可以使用 sqlmock 模拟执行 db.Find 调用时返回的结果。If you just want to test the SQL string that
db.Find
returns, you can use theDryRun
feature (per documentation).However, to write a test for the
getURL
function, you could use sqlmock to mock the results that would be returned when executing thedb.Find
call.这篇文章和Emin Laletovic 对我帮助很大。
我想我得到了这个问题的答案。
让我们回顾一下这个问题。首先,我使用 gorm 作为 postgresql,使用 mux 作为 http 服务,并构建一个 CRUD 服务。
我需要编写一个单元测试来检查我的数据库语法是否正确(我们假设连接状态为OK),因此我们重点讨论如何编写SQL语法的单元测试。
但是main.go中的处理函数没有返回值,所以我们需要使用mock-sql/ExpectQuery(),当
db.Find()时会触发该函数
内getURL()
。通过这样做,我们不必返回一个值来检查它是否与我们的目标匹配。我在更新中遇到的问题已通过这篇文章< /a>,使用 mux 构建单元测试,但该帖子重点关注状态检查和返回值。
我为此测试设置了 const 答案,id 变量是我们期望得到的。注意到$1我不知道如何更改它,并且我尝试了很多次重写但SQL语法仍然是return $1,也许这是某种我不知道的约束。
我通过 doint this 将值传递到
getURL()
最后,我们调用
mock.ExpectationsWereMet()
来检查是否出现任何问题。下面是我的测试代码:
我使用 args(1, 1) 和 args(1, 2) 运行了两个测试,效果很好。见下图(请忽略中文)
This Post and Emin Laletovic are really helps me alot.
I think I get the answer to this qeustion.
Let's recap this questioon. First, I'm using gorm for postgresql and mux for http services and build a CRUD service.
I need to write a unit test to check if my database syntax is correct (we assuming that the connection is statusOK), so we focus on how to write a unit test for SQL syntax.
But the handler function in main.go don't have return value, so we need to use mock-sql/
ExpectQuery()
, this function will be triggered when thedb.Find()
insidegetURL()
. By doing this, we dont have to return a value to check if it match our target or not.The problem I met in Update is fixed by This Post, building an unit test with mux, but that post is focusing on status check and return value.
I set the const answer for this test, the id variable is what we expect to get. Noticed that $1 I don't know how to change it, and I've try many times to rewrite but SQL syntax is still return $1, maybe it is some kind of constraint I dont know.
I set the value pass into the
getURL()
by doint thisFinally, we call
mock.ExpectationsWereMet()
to check if anything went wrong.Below is my test code:
And I run two tests with args(1, 1) and args(1, 2), and it works fine. see pic below(please ignore the chinese words)