4

I'm trying to use the mtest package (https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo/integration/mtest) to perform some testing with mock results on my MongoDB calls, but I can't seem to figure out how to properly mock the *mongo.UpdateResult value that gets returned when you make an UpdateOne(...) call on a collection.

Here is a snippet demonstrating the problem:

package test

import (
    "context"
    "errors"
    "testing"

    "github.com/stretchr/testify/assert"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/integration/mtest"
)

func UpdateOneCall(mongoClient *mongo.Client) error {
    filter := bson.D{{Key: "SomeIDField", Value: "SomeID"}}
    update := bson.D{{Key: "$set", Value: bson.D{{Key: "ANewField", Value: true}}}}
    collection := mongoClient.Database("SomeDatabase").Collection("SomeCollection")
    updateResult, err := collection.UpdateOne(context.Background(), filter, update)
    if err != nil {
        return err
    }
    if updateResult.ModifiedCount != 1 {
        return errors.New("no field was updated")
    }
    return nil
}

func TestUpdateOneCall(t *testing.T) {
    mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
    defer mt.Close()

    mt.Run("Successful Update", func(mt *mtest.T) {

        mt.AddMockResponses(mtest.CreateSuccessResponse(
            bson.E{Key: "NModified", Value: 1},
            bson.E{Key: "N", Value: 1},
        ))

        err := UpdateOneCall(mt.Client)

        assert.Nil(t, err, "Should have successfully triggered update")
    })
}

The collection.UpdateOne(context.Background(), filter, update) call works perfectly fine. There are no errors returned. Unfortunately, the updateResult.ModifiedCount value is always 0.

I've tried multiple combinations of mtest.CreateSuccessResponse(...) and bson.D, utilizing names such as NModified and N (as can be seen in the snippet), as well as ModifiedCount and MatchedCount. Nothing seems to do the trick.

Is there anyway to mock this call such that it actually returns a value for the ModifiedCount?

Francis Bartkowiak
  • 1,374
  • 2
  • 11
  • 28

2 Answers2

2
mt.AddMockResponses(bson.D{
            {"ok", 1},
            {"nModified", 1},
        })

This worked for me to get ModifiedCount : 1

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 22 '22 at 14:10
  • The fact that it just came down to case-sensitivity makes me upset. Good find! This was absolutely the solution. Just need to use `nModified` and `n` instead of `NModified` and `N`. Also, for anyone interested, you can still use the `mtest.CreateSuccessResponse` as well, so it can look like this: `mt.AddMockResponses(mtest.CreateSuccessResponse(bson.E{Key: "nModified", Value: 1},bson.E{Key: "n", Value: 1}))` – Francis Bartkowiak Dec 23 '22 at 14:10
1

@Vishwas Mallikarjuna got the right answer, so I'm leaving their post as the accepted one because they absolutely deserve that. However, given their findings, I just wanted to expand a little bit.

The issue came down to case-sensitivity. Now that I know that, I was able to mock the MatchedCount, ModifiedCount, UpsertedCount, and the UpsertedID.

This chunk of code shows how you would go about influencing all of these values:

package test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/assert"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/integration/mtest"
    "go.mongodb.org/mongo-driver/x/mongo/driver/operation"
)

const (
    mockMatchedCount            int64  = 5
    oneLessThanMockedMatchCount int64  = 4
    mockModifiedCount           int64  = 22
    mockUpsertedCount           int64  = 13
    mockUpsertedID              string = "CouldBeAnythingIThink"
)

func UpdateOneCall(mongoClient *mongo.Client) (*mongo.UpdateResult, error) {
    filter := bson.D{{Key: "SomeIDField", Value: "SomeID"}}
    update := bson.D{{Key: "$set", Value: bson.D{{Key: "ANewField", Value: true}}}}
    collection := mongoClient.Database("SomeDatabase").Collection("SomeCollection")
    return collection.UpdateOne(context.Background(), filter, update)
}

func TestUpdateOneCall(t *testing.T) {
    mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
    defer mt.Close()

    mt.Run("Successful Update", func(mt *mtest.T) {

        upsertedVals := make([]operation.Upsert, mockUpsertedCount)
        upsertedVals[0] = operation.Upsert{ID: mockUpsertedID}
        mt.AddMockResponses(mtest.CreateSuccessResponse(
            bson.E{Key: "n", Value: mockMatchedCount},
            bson.E{Key: "nModified", Value: mockModifiedCount},
            bson.E{Key: "upserted", Value: upsertedVals},
        ))

        result, err := UpdateOneCall(mt.Client)

        assert.Nil(t, err, "Should have successfully triggered update")
        assert.Equal(t, result.MatchedCount, oneLessThanMockedMatchCount)
        assert.Equal(t, result.ModifiedCount, mockModifiedCount)
        assert.Equal(t, result.UpsertedCount, mockUpsertedCount)
        assert.Equal(t, result.UpsertedID, mockUpsertedID)
    })
}

Also, if you're wondering why the actual result.MatchedCount is oneLessThanMockedMatchCount, it comes down to how having an Upserted value works. If you go through the logic, in the file go.mongodb.org/mongo-driver@v1.10.2/mongo/collection.go you'll find this chunk of code that explains it:

opRes := op.Result()
res := &UpdateResult{
    MatchedCount:  opRes.N,
    ModifiedCount: opRes.NModified,
    UpsertedCount: int64(len(opRes.Upserted)),
}
if len(opRes.Upserted) > 0 {
    res.UpsertedID = opRes.Upserted[0].ID
    res.MatchedCount--
}

return res, err
Francis Bartkowiak
  • 1,374
  • 2
  • 11
  • 28