1

I'm trying to create a unit test for the following function that is making database calls internally using Postgres driver:

type DBer interface {
    Exec(query string, args ...interface{}) (sql.Result, error)
    Query(query string, args ...interface{}) (interface{}, error)
    QueryRow(query string, args ...interface{}) *sql.Row
    Prepare(query string) (*sql.Stmt, error)
}

type AppInfoCtrl struct {
    DB DBer
}

type Rower interface {
    Next() bool
    Close() error
    Scan(...interface{}) error
}

func parseRows(rows Rower, infos []AppInfo) ([]AppInfo, error) {
    defer rows.Close()
    for rows.Next() {
        var i AppInfo
        if err := rows.Scan(&i.Id, &i.Platform); err != nil {
            return nil, err
        }
        infos = append(infos, i)
    }
    return infos, nil
}

func (a *AppInfoCtrl) List() ([]AppInfo, error) {
    query := "select id, platform from appinfo where deleted = false"
    rows, err := a.DB.Query(query)
    if err != nil {
        return nil, err
    }

    appInfos := []AppInfo{}
    parseRows(rows, appInfos)

    return appInfos, nil
}

And my test looks like this:

func (f *FakeDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
    fmt.Println(query)
    var rows *sql.Rows
    return rows, nil
}

However, after running this I get the following compilation error:

appinfo/controller.go:68:11: cannot use rows (type interface {}) as type Rower in argument to parseRows:
        interface {} does not implement Rower (missing Close method)

When I look at the source code, sql.Rows does implement Close():

https://golang.org/pkg/database/sql/#Rows.Close

Any idea what I need to do here? Am I even taking the right approach to test List() here? I'm not particularly picky about testing parseRows() as it only contains calls to db.Rows, but I need to at least test List here.

0

1 Answer 1

3

The problem is that DBer.Query returns a generic interface{}, which the compiler cannot assume has any methods at all:

Query(query string, args ...interface{}) (interface{}, error)

Meaning, unless you use a type assertion, you cannot call any methods on the returned value. Perhaps it should instead return (Rower, error)?

The compiler, in effect, sees this:

rows, err := a.DB.Query(query)

rows is a interface{}. It could be anything. It could be an int. It cannot assume it has any methods at all. So when you pass it to parseRows:

parseRows(rows, appInfos)

Defined as:

func parseRows(rows Rower, infos []AppInfo) ([]AppInfo, error)

Well, it takes a Rower, but you're passing an interface{}, which is not guaranteed to implement Rower. Hence the compiler error you're getting:

interface {} does not implement Rower (missing Close method)

It has nothing to do with the underlying value, but rather the type of the variable. The variable is of type interface{}, which has no methods, and therefor does not implement Rower.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the quick response. I changed the type to Rower, but I got this error: *sql.DB does not implement appinfo.DBer (wrong type for Query method) have Query(string, ...interface {}) (*sql.Rows, error) want Query(string, ...interface {}) (appinfo.Rower, error) That's why I ended up changing the return type to *sql.Rows. However, when I run my tests rows.Close() panics with this: panic: runtime error: invalid memory address or nil pointer dereference I need to figure out how to create a fake *sql.Rows that doesn't panic when Close() is called,
You'll actually have to mock it rather than just returning a nil pointer to the real type.
sql.Rows is a rather big struct. golang.org/src/database/sql/sql.go?s=71562:72284#L2539 I wish it were an interface. I'm scratching my head to figure out a way to mock it now.
You don't need to wish it were an interface. Just create the interface you need and use it in place of sql.Rows in your types. That's the beauty of Go's duck-typed interfaces - the implementation doesn't have to know about the interface.
@mohi666 it's actually simple, take a look at this: play.golang.org/p/57fhkok0kEN (fixed link). Using this your mock implementation of Dber can return a mock implementation of Rower, and anytime you need the real thing use the adapter.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.