模拟 go 方法
Mocking go methods
我正在写一个小的 POC 去上班,但我似乎无法弄清楚模拟技术。
这就是我目前所拥有的...
connect.go
package db
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"strings"
_ "github.com/lib/pq"
)
type config map[string]interface{}
type DbConn struct {
db db
}
type db interface {
getConnectionStringFromConfig(file string) (connStr string, err error)
}
func NewDbConn(d db) *DbConn {
return &DbConn{db: d}
}
func getConnectionStringFromConfig(file string) (connStr string, err error) {
var c config
var bt []byte
if bt, err = ioutil.ReadFile(file); err != nil {
fmt.Printf("Error Reading config file: %v", err)
return
}
fmt.Println("Finish reading file. Going to construct a connection string")
if err = json.Unmarshal(bt, &c); err != nil {
fmt.Printf("Error unmarshalling config file: %v", err)
return
}
connStr = strings.TrimLeft(getConfigAsString(c), " ")
return
}
func getConfigAsString(c config) (connStr string) {
for k, v := range c {
connStr += strings.Join([]string{" " + k, v.(string)}, "=")
}
return
}
// Connect database connection
func (dbConn DbConn) Connect() (conn *sql.DB, err error) {
fmt.Println("Starting database connection...")
connStr, err := getConnectionStringFromConfig("path/to/conf.json")
if err != nil {
return
}
conn, err = sql.Open("some_driver", connStr)
return
}
connect_test.go
package db
import (
"errors"
"testing"
)
type dbConnMock struct {
db dbMock
}
type dbMock interface {
getConnectionStringFromConfig(file string) (connStr string, err error)
}
func (dbm dbConnMock) getConnectionStringFromConfig(file string) (connStr string, err error) {
return "", errors.New("123")
}
// should not throw error when trying to open db connection
func TestDatabaseConnection(t *testing.T) {
dbCon := &DbConn{}
_, err := dbCon.Connect()
if err != nil {
t.Errorf("test failed. \n %d", err)
}
}
func TestDatabaseConnectionFail(t *testing.T) {
var dm dbMock
dbCon := NewDbConn(dm)
_, err := dbCon.Connect()
if err == nil {
t.Errorf("test failed. %d", err)
}
}
如您所见,这是一个简单的数据库连接逻辑,我使用接口对其进行了测试和模拟。
我想覆盖 100% 的代码,所以我必须模拟某些方法。
上面的代码虽然有效,但第二个测试失败了,可能是因为我在尝试模拟它时遗漏了一些东西。
请帮忙..
您可以做一些事情。
简单的方法
如果你想保持简单,你可以做的是让你的模拟结构具有它应该return的字段,并且在每个测试用例中你将这些字段设置为你的模拟应该return 对于那个测试用例。
这样,您可以通过不同的方式使您的模拟成功或失败。
此外,您不需要 dbMock
接口,因为 dbConnMock
实现了 db
接口,这就是您所需要的。
您的模拟可能如下所示:
type dbConnMock struct {
FileCalled string
connStr string
err error
}
func (dbm dbConnMock) getConnectionStringFromConfig(file string) (connStr string, err error) {
dbm.FileCalled = file
return dbm.connStr, dbm.err
}
现在,您可以使用 FileCalled
验证您的方法是否被调用 并使用预期的参数,并且您可以使它具有您想要的 行为想模拟.
如果你也想确保你的方法只被调用一次,你还可以添加一个计数器来查看它被调用了多少次。
使用模拟库
如果您不想担心编写该逻辑,一些库可以为您完成,例如 testify/mock。
这是一个简单模拟如何使用 testify/mock
工作的示例:
type dbMock struct {
mock.Mock
}
func (m *dbMock) getConnectionStringFromConfig(file string) (string, error) {
args := m.Called(file)
return args.String(0), args.Error(1)
}
func TestSomething(t *testing.T) {
tests := []struct {
description string
connStr string
err error
expectedFileName string
// add expected outputs and inputs of your tested function
}{
{
description: "passing test",
connStr: "valid connection string",
err: nil,
expectedFileName: "valid.json",
},
{
description: "invalid file",
connStr: "",
err: errors.New("invalid file"),
expectedFileName: "invalid.json",
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
dbMock := &dbConnectionMock{}
dbMock.
On("getConnectionStringFromConfig", test.expectedFileName).
Return(test.connStr, test.err).
Once()
thing := &Something{
db: dbMock,
}
output, err := thing.doSomething()
// assert that output and err are expected
dbMock.AssertExpectations(t) // this will make sure that your mock is only used as expected in your test, depending on your `On` calls
})
}
}
此代码确保您的方法被调用一次并使用特定的参数,并将使其成为return您的测试用例中指定的内容。
我正在写一个小的 POC 去上班,但我似乎无法弄清楚模拟技术。 这就是我目前所拥有的...
connect.go
package db
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"strings"
_ "github.com/lib/pq"
)
type config map[string]interface{}
type DbConn struct {
db db
}
type db interface {
getConnectionStringFromConfig(file string) (connStr string, err error)
}
func NewDbConn(d db) *DbConn {
return &DbConn{db: d}
}
func getConnectionStringFromConfig(file string) (connStr string, err error) {
var c config
var bt []byte
if bt, err = ioutil.ReadFile(file); err != nil {
fmt.Printf("Error Reading config file: %v", err)
return
}
fmt.Println("Finish reading file. Going to construct a connection string")
if err = json.Unmarshal(bt, &c); err != nil {
fmt.Printf("Error unmarshalling config file: %v", err)
return
}
connStr = strings.TrimLeft(getConfigAsString(c), " ")
return
}
func getConfigAsString(c config) (connStr string) {
for k, v := range c {
connStr += strings.Join([]string{" " + k, v.(string)}, "=")
}
return
}
// Connect database connection
func (dbConn DbConn) Connect() (conn *sql.DB, err error) {
fmt.Println("Starting database connection...")
connStr, err := getConnectionStringFromConfig("path/to/conf.json")
if err != nil {
return
}
conn, err = sql.Open("some_driver", connStr)
return
}
connect_test.go
package db
import (
"errors"
"testing"
)
type dbConnMock struct {
db dbMock
}
type dbMock interface {
getConnectionStringFromConfig(file string) (connStr string, err error)
}
func (dbm dbConnMock) getConnectionStringFromConfig(file string) (connStr string, err error) {
return "", errors.New("123")
}
// should not throw error when trying to open db connection
func TestDatabaseConnection(t *testing.T) {
dbCon := &DbConn{}
_, err := dbCon.Connect()
if err != nil {
t.Errorf("test failed. \n %d", err)
}
}
func TestDatabaseConnectionFail(t *testing.T) {
var dm dbMock
dbCon := NewDbConn(dm)
_, err := dbCon.Connect()
if err == nil {
t.Errorf("test failed. %d", err)
}
}
如您所见,这是一个简单的数据库连接逻辑,我使用接口对其进行了测试和模拟。 我想覆盖 100% 的代码,所以我必须模拟某些方法。 上面的代码虽然有效,但第二个测试失败了,可能是因为我在尝试模拟它时遗漏了一些东西。 请帮忙..
您可以做一些事情。
简单的方法
如果你想保持简单,你可以做的是让你的模拟结构具有它应该return的字段,并且在每个测试用例中你将这些字段设置为你的模拟应该return 对于那个测试用例。
这样,您可以通过不同的方式使您的模拟成功或失败。
此外,您不需要 dbMock
接口,因为 dbConnMock
实现了 db
接口,这就是您所需要的。
您的模拟可能如下所示:
type dbConnMock struct {
FileCalled string
connStr string
err error
}
func (dbm dbConnMock) getConnectionStringFromConfig(file string) (connStr string, err error) {
dbm.FileCalled = file
return dbm.connStr, dbm.err
}
现在,您可以使用 FileCalled
验证您的方法是否被调用 并使用预期的参数,并且您可以使它具有您想要的 行为想模拟.
如果你也想确保你的方法只被调用一次,你还可以添加一个计数器来查看它被调用了多少次。
使用模拟库
如果您不想担心编写该逻辑,一些库可以为您完成,例如 testify/mock。
这是一个简单模拟如何使用 testify/mock
工作的示例:
type dbMock struct {
mock.Mock
}
func (m *dbMock) getConnectionStringFromConfig(file string) (string, error) {
args := m.Called(file)
return args.String(0), args.Error(1)
}
func TestSomething(t *testing.T) {
tests := []struct {
description string
connStr string
err error
expectedFileName string
// add expected outputs and inputs of your tested function
}{
{
description: "passing test",
connStr: "valid connection string",
err: nil,
expectedFileName: "valid.json",
},
{
description: "invalid file",
connStr: "",
err: errors.New("invalid file"),
expectedFileName: "invalid.json",
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
dbMock := &dbConnectionMock{}
dbMock.
On("getConnectionStringFromConfig", test.expectedFileName).
Return(test.connStr, test.err).
Once()
thing := &Something{
db: dbMock,
}
output, err := thing.doSomething()
// assert that output and err are expected
dbMock.AssertExpectations(t) // this will make sure that your mock is only used as expected in your test, depending on your `On` calls
})
}
}
此代码确保您的方法被调用一次并使用特定的参数,并将使其成为return您的测试用例中指定的内容。