DATA-DOG / go-sqlmock

Sql mock driver for golang to test database interactions
Other
6.05k stars 406 forks source link

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

Closed mrdulin closed 3 months ago

mrdulin commented 4 years ago

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.

dolmen commented 4 years ago

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 commented 4 years ago

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)