Mock vs Fake and Classical Testing

Mock Approach

Given a code like this:

type Obj struct {
*sql.DB // or Provider
}
func (o *Obj) DoMultipleQuery(in InputStruct) (out OutputStruct, err error) {
... = o.DoSomeQuery()
... = o.DoOtherQuery()
}
func TestObjDoMultipleQuery(t *testing.T) {
o := Obj{mockProvider{}}
testCases := []struct {
name string
mockFunc func(sqlmock.Sqlmock, *gomock.Controller) in InputStruct out OutputStruct
shouldErr bool
} {
{
name: `bast case`,
mockFunc: func(db sqlmock.SqlMock, c *gomock.Controller) {
db.ExpectExec(`UPDATE t1 SET bla = \?, foo = \?, yay = \? WHERE bar = \? LIMIT 1`).
WillReturnResult(sqlmock.NewResult(1,1))
db.ExpectQuery(`SELECT a, b, c, d, bar, bla, yay FROM t1 WHERE bar = \? AND state IN \(1,2\)`).
WithArgs(3).
WillReturnRows(sqlmock.NewRows([]string{"id", "channel_name", "display_name", "color", "description", "active", "updated_at"}).
AddRow("2", "bla2", "Bla2", "#0000", "bla bla", "1", "2021-05-18T15:04:05Z").
AddRow("3", "wkwk", "WkWk", "#0000", "wkwk", "1", "2021-05-18T15:04:05Z"))
...
}, in: InputStruct{...}, out: OutputStruct{...},
wantErr: false,
},
{
... other cases
},
} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T){ ... // prepare mock object o := Obj{mockProvider} out := o.DoMultipleQueryBusinessLogic(tc.in) assert.Equal(t, out, tc.out) }) }
}
func SqlToRegexSql(sql string) string {
return // replace special characters in regex (, ), ?, . with escaped version
}
db.ExpectQuery(SqlToRegexSql(ORIGINAL_QUERY)) ...

Fake Approach

Fake testing use classical approach, instead of checking implementation detail (expected calls to dependency), we use a compatible implementation as dependency (eg. a slice/map of struct for database table/DataProvider)

Classical Approach for DataProvider

It’s better to test the queries using classical (black box) approach integration test instead of mock (white box), since mock and fake testing can only test the correctness of business logic, not logic of the data provider that mostly depend to a 2nd party (database). Fake testing also considered a classical approach, since it test input/output not implementation detail.

Conclusion

  1. use mock for databases with eventual consistency
  2. prefer fake over mock for business logic correctness because it’s better for maintainability to test behavior (this input should give this output), instead of implementation details
  3. prefer classical testing over mock testing for checking data provider logic correctness

References

(aka confirmation bias :3)

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store