Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking official MongoDb driver

I need to define these interfaces to mock official mongo driver

type MgCollection interface {   
    FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) *mongo.SingleResult
    // Other methods
}

type MgDatabase interface {
    Collection(name string, opts ...*options.CollectionOptions) MgCollection
    // Other methods
}

In mongo driver package there are two structs mongo.Collection and mongo.Database with these methods

func (coll *Collection) FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) *SingleResult {
    // Method code
}

func (db *Database) Collection(name string, opts ...*options.CollectionOptions) *Collection {
    // Method code
}

The struct *mongo.Collection implement MgCollection correctly, so this code compiles without error

var col mgdriver.MgCollection
col = &mongo.Collection{}
col.FindOne(ctx, nil, nil)

But The struct *mongo.Database not implement MgDatabase, so I when I write something like this:

var db mgdriver.MgDatabase
db = &mongo.Database{}
db.Collection("Test", nil)

Compiler show this error:

cannot use &mongo.Database literal (type *mongo.Database) as type mgdriver.MgDatabase in assignment: *mongo.Database does not implement mgdriver.MgDatabase (wrong type for Collection method) have Collection(string, ...*options.CollectionOptions) *mongo.Collection want Collection(string, ...*options.CollectionOptions) mgdriver.MgCollection

Both mongo.Collection and mongo.Database are in the official package and I cannot change any code in that package. so how can I change my interfaces to mock official mongo driver correctly?

like image 605
Vahid Jafari Avatar asked Dec 31 '22 16:12

Vahid Jafari


1 Answers

Usually, you do not. What you should do is to define a data access interface,

type CRUD interface {
  Create(yourModel) error
  Read(page, size, skip) []yourModel
  Update(yourModel) error
  Delete(yourModel) error
}

and implement it.

You then mock the interface, for example with testify/mock:

type MockedCRUD struct {
  mock.Mock
}

func(m *MockedCRUD)Create(y yourModel) error{
  returned m.Called(y).Error(0)
}
// And so on and so forth

Since MockedCRUD satisfies the CRUD interface, you can use it as you would use your MongoCRUD implementation, without any hassle:

func TestYourApplicationLogicCallingCreate( t *testing.T){
    model := YourModel{Type: ”Baz”})

    mocked := new(MockedCRUD)
    mocked.On(”Create”,model).Return(ErrInvalidType)

    app := YourApplication{CRUD:mocked}

    err := app.yourApplicationLogicCallingCreate(model)

    assert.Error(t,err)
    assert.Equal(t,ErrInvalidType,err)

}

Remains the question how do you test the implementation of the CRUD interface. I used to use the mgo driver, originally developed by Gustavo Niemeyer and taken over by globalsign. That brought a nifty little package called dbtest. It is actually a very thin wrapper around a MongoDB instance, starting and stopping one on demand, with the ability to reset the data in between tests. Either just import dbtest or, to quote a Go Proverb

A little bit of copy is better than a little bit of dependency.

(Remember to give credit, though, and retain the copyright notes.)

So, using the method described above you can do your unit tests pretty fast against a mock, with stable and predictable answers tailored to your tests and only do relatively expensive and slow tests against MongoDB only where you absolutely must.

Added bonus: it is relatively easy to swap out your actual persistence technology.

like image 87
Markus W Mahlberg Avatar answered Jan 05 '23 14:01

Markus W Mahlberg