examples for the mongo-go-driver mock
在使用 mongo-go-driver 时,需要对mongo数据库操作模拟单元测试用例,
使用 testify 或 genmock 中的 mock 都需要实现mock接口。
而 mongo-go-driver 官方有一个 mtest 包,提供更好的实现。
mock单元测试
下面是mock具体操作单元测试示例:
MongoDB 查询操作示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import (
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func getFromID(id primitive.ObjectID) (*user, error) {
filter := bson.D{{Key: "id", Value: id}}
var object user
if err := userCollection.FindOne(context.Background(), filter).Decode(&object); err != nil {
return nil, err
}
return &object, nil
}
|
为查询操作实现单元测试示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import (
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
)
func TestFindOne(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
defer mt.Close()
mt.Run("success", func(mt *mtest.T) {
userCollection = mt.Coll
expectedUser := user{
ID: primitive.NewObjectID(),
Name: "john",
Email: "john.doe@test.com",
}
mt.AddMockResponses(mtest.CreateCursorResponse(1, "foo.bar", mtest.FirstBatch, bson.D{
{"_id", expectedUser.ID},
{"name", expectedUser.Name},
{"email", expectedUser.Email},
}))
userResponse, err := getFromID(expectedUser.ID)
assert.Nil(t, err)
assert.Equal(t, &expectedUser, userResponse)
})
}
|
如下创建一个测试实例,测试类型指定为 mtest.Mock
1
|
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
|
指定 mtest.Mock 类型时,不会创建真实数据库连接,对于查询结果需要通过CreateCursorResponse
来模拟。
模拟多行响应结果集:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
id1 := primitive.NewObjectID()
id2 := primitive.NewObjectID()
first := mtest.CreateCursorResponse(1, "foo.bar", mtest.FirstBatch, bson.D{
{"_id", id1},
{"name", "john"},
{"email", "john.doe@test.com"},
})
second := mtest.CreateCursorResponse(1, "foo.bar", mtest.NextBatch, bson.D{
{"_id", id2},
{"name", "john"},
{"email", "foo.bar@test.com"},
})
killCursors := mtest.CreateCursorResponse(0, "foo.bar", mtest.NextBatch)
mt.AddMockResponses(first, second, killCursors)
|
更多操作 mongo-go-driver-mock
非mock单元测试
mtest.Mock
类型虽然可以模拟结果集,确不能模拟执行过程,响应结果都是期望中的数据,不能体现过滤条件等操作。
这个时候就需要其他类型来实现:
1
2
3
4
5
6
7
8
9
10
11
12
|
const (
// Default specifies a client to the connection string in the MONGODB_URI env variable with command monitoring
// enabled.
Default ClientType = iota
// Pinned specifies a client that is pinned to a single mongos in a sharded cluster.
Pinned
// Mock specifies a client that communicates with a mock deployment.
Mock
// Proxy specifies a client that proxies messages to the server and also stores parsed copies. The proxied
// messages can be retrieved via T.GetProxiedMessages or T.GetRawProxiedMessages.
Proxy
)
|
mtest提供4种类型选择,除Mock外的三种类型都是会创建真实连接,
这种时候在测试前需要编好测试数据,在执行测试方法前写入数据库。
通过name查询方法find
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func find(name string) ([]user, error) {
filter := bson.D{{Key: "name", Value: name}}
var users []user
cursor, err := userCollection.Find(context.Background(), filter)
if err != nil {
return nil, err
}
if err = cursor.All(context.Background(), &users); err != nil {
return nil, err
}
return users, nil
}
|
方法find的单元测试示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
import (
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
. "github.com/smartystreets/goconvey/convey"
_ "github.com/joho/godotenv/autoload"
)
func TestFindOne(t *testing.T) {
Convey("Find", t, func() {
err := mtest.Setup(mtest.NewSetupOptions().SetURI(os.Getenv("MONGODB_URI")))
assert.Nil(t, err)
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Default).
DatabaseName("test").CollectionName("test").AtlasDataLake(true))
defer mt.Close()
mt.RunOpts("Find", mtest.NewOptions().ClientType(mtest.Default).
DatabaseName("test").CollectionName("test").AtlasDataLake(true), func(mt *mtest.T) {
user1 := user{
// ID: primitive.NewObjectID(),
Name: "john",
Email: "john.doe@test.com",
}
user2 := user{
// ID: primitive.NewObjectID(),
Name: "johnx",
Email: "johnx.doe@test.com",
}
ctx := mtest.Background
res, err := mt.Coll.InsertOne(ctx, &user1)
assert.Nil(t, err)
id1, ok := res.InsertedID.(primitive.ObjectID)
assert.Equal(t, ok, true)
res, err = mt.Coll.InsertOne(ctx, &user2)
assert.Nil(t, err)
// id2, ok := res.InsertedID.(primitive.ObjectID)
// assert.Equal(t, ok, true)
userList, err := find(user1.Name)
assert.Nil(t, err)
user1.ID = id1
assert.Equal(t, 1, len(userList))
assert.Equal(t, []user{user1}, userList)
})
})
}
|
通过当前测试目录.env
文件中配置环境变量MONGODB_URI
连接数据库。
1
2
|
MONGODB_URI=mongodb://mongo.example.com:27017
ATLAS_DATA_LAKE_INTEGRATION_TEST=true
|
然后通过mtest.Options
指定数据库和集合,然后模拟写入2条name不同的数据,
调用find方法时,结果集中只会有1条数据返回。
值得注意的时,在非mtest.Mock
下,结束时都会有清理数据库操作,
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func (t *T) RunOpts(name string, opts *Options, callback func(mt *T)) {
t.T.Run(name, func(wrapped *testing.T) {
sub := newT(wrapped, t.baseOpts, opts)
...
// defer dropping all collections if the test is using a client
defer func() {
...
if sub.clientType != Mock {
sub.ClearFailPoints()
sub.ClearCollections()
}
|
可以看到非 Mock 类型,会调用 ClearCollections
, defer mt.Close()
操作也一样。
所以mtest.Options
中也可以不指定DatabaseName("test").CollectionName("test")
,不指定时就会使用默认的。
1
2
3
4
|
const (
// TestDb specifies the name of default test database.
TestDb = "test"
)
|
如果不想清理数据库中的数据怎么办呢,在设置Options时,设置 AtlasDataLake(true)
即可。
ClearCollections
1
2
3
4
|
// ClearCollections drops all collections previously created by this test.
func (t *T) ClearCollections() {
// Collections should not be dropped when testing against Atlas Data Lake because the data is pre-inserted.
if !testContext.dataLake {
|
这样集合中的数据在测试结束后,就不会被清理。
也可以通过环境变量 ATLAS_DATA_LAKE_INTEGRATION_TEST
设置
1
2
3
4
5
6
7
|
// Setup initializes the current testing context.
// This function must only be called one time and must be called before any tests run.
func Setup(setupOpts ...*SetupOptions) error {
...
testContext.dataLake = os.Getenv("ATLAS_DATA_LAKE_INTEGRATION_TEST") == "true"
|
参考