在Go中模拟MongoDB响应

I'm fetching a document from MongoDB and passing it into function transform, e.g.

var doc map[string]interface{}
err := collection.FindOne(context.TODO(), filter).Decode(&doc) 
result := transform(doc)

I want to write unit tests for transform, but I'm not sure how to mock a response from MongoDB. Ideally I want to set something like this up:

func TestTransform(t *testing.T) {
    byt := []byte(`
    {"hello": "world",
     "message": "apple"}
 `)

    var doc map[string]interface{}

    >>> Some method here to Decode byt into doc like the code above <<<

    out := transform(doc)
    expected := ...
    if diff := deep.Equal(expected, out); diff != nil {
        t.Error(diff)
    }
}

One way would be to json.Unmarshal into doc, but this sometimes gives different results. For example, if the document in MongoDB has an array in it, then that array is decoded into doc as a bson.A type not []interface{} type.

The best solution to write testable could would be to extract your code to a DAO or Data-Repository. You would define an interface which would return what you need. This way, you can just used a Mocked Version for testing.

// repository.go
type ISomeRepository interface {
    Get(string) (*SomeModel, error)
}

type SomeRepository struct { ... }

func (r *SomeRepository) Get(id string) (*SomeModel, error) {
    // Handling a real repository access and returning your Object
}

When you need to mock it, just create a Mock-Struct and implement the interface:

// repository_test.go

type SomeMockRepository struct { ... }

func (r *SomeRepository) Get(id string) (*SomeModel, error) {
    return &SomeModel{...}, nil
}

func TestSomething() {
    // You can use your mock as ISomeRepository
    var repo *ISomeRepository
    repo = &SomeMockRepository{}
    someModel, err := repo.Get("123")
}

This is best used with some kind of dependency-injection, so passing this repository as ISomeRepository into the function.

Using monkey library to hook any function from mongo driver.

For example:

func insert(collection *mongo.Collection) (int, error) {
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)

    u := User{
        Name: "kevin",
        Age:  20,
    }

    res, err := collection.InsertOne(ctx, u)
    if err != nil {
        log.Printf("error: %v", err)
        return 0, err
    }

    id := res.InsertedID.(int)
    return id, nil
}

func TestInsert(t *testing.T) {
    var c *mongo.Collection

    var guard *monkey.PatchGuard
    guard = monkey.PatchInstanceMethod(reflect.TypeOf(c), "InsertOne",
        func(c *mongo.Collection, ctx context.Context, document interface{}, opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) {
            guard.Unpatch()
            defer guard.Restore()

            log.Printf("record: %+v, collection: %s, database: %s", document, c.Name(), c.Database().Name())
            res := &mongo.InsertOneResult{
                InsertedID: 100,
            }

            return res, nil
        })

    collection := client.Database("db").Collection("person")
    id, err := insert(collection)

    require.NoError(t, err)
    assert.Equal(t, id, 100)
}