go-sqlmock icon indicating copy to clipboard operation
go-sqlmock copied to clipboard

Is it necessary to call reflect.DeepEqual in the unit test?

Open mrdulin opened this issue 6 years ago • 2 comments
trafficstars

I am not clearly how it works sqlmock internal. Here is my case:

A insert function:

func insert() (sql.Result, error) {
        query := `...`
  	stmt, err := repo.Db.Prepare(query)
	if err != nil {
		return nil, errors.Wrap(err, "repo.Db.Prepare(query)")
	}
	defer stmt.Close()

	result, err := stmt.Exec(values...)
	if err != nil {
		return nil, errors.Wrap(err, "repo.Db.Exec(query, values)")
	}
	return result, nil
}

As you can see, the return value of this function is sql.Result and error.

After I add below statements in my unit test, this test failed.

if !reflect.DeepEqual(got, tt.want) {
  t.Errorf("repo.Upsert() = %#v, want %#v", got, tt.want)
}

Got error:

repo.Upsert() = sql.driverResult{Locker:(*sql.driverConn)(0xc0000ae080), resi:(*sqlmock.result)(0xc00000c140)}, want &sqlmock.result{insertID:1, rowsAffected:1, err:error(nil)}

I mock the return result using WillReturnResult(sqlmock.NewResult(1, 1)). And the value of tt.want is sqlmock.NewResult(1, 1) as well.

The return value of sqlmock.NewResult is driver.Result, so I think that's why the error happened.

Is it necessary add !reflect.DeepEqual(got, tt.want) and assert it ? Or, all I need is mock.ExpectationsWereMet()? Thanks for explaining.

mrdulin avatar Oct 17 '19 13:10 mrdulin

database/sql.Result and database/sql/driver.Result are interfaces. They expose the same methods. So they are the same.

DeepEqual checks deeply that the concrete types are the same. But as you are dealing with interfaces, that's not what matters for your unit test. Instead you are just interested in checking that the values returned by each method are what you expect:

id1, err1 := got.LastInsertId()
id2, err2 := tt.want.LastInsertId()
switch {
case err1 != nil:
    if err2 == nil || err2.Error() != err1.Error() {
        t.Fatalf("LastInsertId: error mismatch %q vs %q", err1, err2)
    }
case err2 != nil:
        t.Fatalf("LastInsertId: error mismatch nil vs %q", err2)
case id1 != id2:
        t.Fatal("LastInsertId: id mismatch %d vs %d", id1, id2)
}
// Same for RowsAffected

dolmen avatar Nov 06 '19 13:11 dolmen

My code above can be refactored:

check := func(m string, f1 func() (int64, error), f2 func() (int64, error)) {
    v1, err1 := f1()
    v2, err2 := f2()
    switch {
    case err1 != nil:
        if err2 == nil || err2.Error() != err1.Error() {
            t.Fatalf(m+": error mismatch %q vs %q", err1, err2)
        }
    case err2 != nil:
        t.Fatalf(m+": error mismatch nil vs %q", err2)
    case v1 != v2:
        t.Fatal(m+": id mismatch %d vs %d", v1, v2)
    }
}

check("LastInsertId", got.LastInsertId, tt.want.LastInsertId)
check("RowsAffected", got.RowsAffected, tt.want.RowsAffected)

dolmen avatar Nov 06 '19 13:11 dolmen