英文:
How to mock db ping without sqlmock
问题
我想了解如何重构我的代码,使用mock.Mock
来模拟db.Ping()
。首先,我想先了解这个概念,而不使用框架。
这是我想要测试的代码:
func Connect() (*sql.DB, error) {
db, err := sql.Open("mysql", "root:secret@tcp(s-maria-db)/s_db")
if err != nil {
return nil, err
}
for i := 0; i < 60; i++ {
// 我希望模拟这部分
if err := db.Ping(); err == nil {
break
}
time.Sleep(time.Second)
}
return db, nil
}
我尝试了这个帖子https://stackoverflow.com/questions/57723476/how-to-mock-a-ping-command,但它实际上没有回答我的问题。如果有答案,我不理解该解决方案如何适用于我的代码。
英文:
I would like to understand how to refactor my code to mock db.Ping()
using mock.Mock
for example. I would like to understand the concept without a framework first.
Here is the code I want to test:
func Connect() (*sql.DB, error) {
db, err := sql.Open("mysql", "root:secret@tcp(s-maria-db)/s_db")
if err != nil {
return nil, err
}
for i := 0; i < 60; i++ {
// I am hoping to mock this portion
if err := db.Ping(); err == nil {
break
}
time.Sleep(time.Second)
}
return db, nil
}
I attempted this post https://stackoverflow.com/questions/57723476/how-to-mock-a-ping-command, but this has not actually answered the question. If it does, I do not understand the solution as it applies to my code.
答案1
得分: 1
你需要使用一个单独的函数来进行ping操作,并且它需要接受一个接口,这样你就可以有条件地传入一个真实的*sql.DB
或者一个模拟对象。这是因为你不能在一个结构体上覆盖一个方法。
// Connect函数打开与数据库的连接。
func Connect() (*sql.DB, error) {
return sql.Open("mysql", "root:secret@tcp(s-maria-db)/s_db")
}
// Pinger定义了一个ping操作的接口。
type Pinger interface {
Ping() error
}
// Ping函数尝试对数据库进行ping操作,在失败之前尝试多次。
func Ping(p Pinger) error {
const maxAttempts = 60
var err error
for i := 0; i < maxAttempts; i++ {
if err = p.Ping(); err == nil {
return nil
}
if i < maxAttempts - 1 {
time.Sleep(time.Second)
}
}
return err
}
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
db, err := Connect()
if err != nil {
return fmt.Errorf("连接到数据库时出错:%w", err)
}
defer db.Close()
if err = Ping(db); err != nil {
return fmt.Errorf("ping数据库时出错:%w", err)
}
...
}
type mockDB struct {
mock.Mock
}
func (m *mockDB) Ping() error {
args := m.Called()
return args.Error(0)
}
func TestPing(t *testing.T) {
db := &mockDB{}
db.On("Ping").Return(...)
err := Ping(db)
...
db.AssertExpectations(t)
}
英文:
You need to use a separate function for pinging, and it needs to accept an interface so you can conditionally pass in a real *sql.DB
or a mock. This is because you can't override a method on a struct.
// Connect opens a connection to the database.
func Connect() (*sql.DB, error) {
return sql.Open("mysql", "root:secret@tcp(s-maria-db)/s_db")
}
// Pinger defines an interface for pinging.
type Pinger interface {
Ping() error
}
// Ping attempts to ping the database, trying several times before failing.
func Ping(p Pinger) error {
const maxAttempts = 60
var err error
for i := 0; i < maxAttempts; i++ {
if err = p.Ping(); err == nil {
return nil
}
if i < maxAttempts - 1 {
time.Sleep(time.Second)
}
}
return err
}
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
db, err := Connect()
if err != nil {
return fmt.Errorf("connecting to db: %w", err)
}
defer db.Close()
if err = Ping(db); err != nil {
return fmt.Errorf("pinging db: %w", err)
}
...
}
type mockDB struct {
mock.Mock
}
func (m *mockDB) Ping() error {
args := m.Called()
return args.Error(0)
}
func TestPing(t *testing.T) {
db := &mockDB{}
db.On("Ping").Return(...)
err := Ping(db)
...
db.AssertExpectations(t)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论