4

In testing database methods, I created a minimal wrapper over the database/sql package to allow me to test against interfaces instead of the difficult if not impossible to setup concrete classes. But, I get the following error when I try to mock sql.Stmt:

cannot use *sql.Stmt as type IStmt in return argument:
    *sql.Stmt does not implement IStmt (wrong type for Query method)
            have Query(...interface {}) (*sql.Rows, error)
            want Query(...interface {}) (IRows, error)

Here are my interfaces:

type IStmt interface {
    Query(args ...interface{}) (IRows, error)
    QueryRow(args ...interface{}) IRow
}

type IRows interface {
    Columns() ([]string, error)
    Next() bool
    Close() error
    Scan(dest ...interface{}) error
    Err() error
}

And here's the problem method:

func (c *DbConnection) Prepare(query string) (IStmt, error) {
    return c.conn.Prepare(query)
}

I know that one of the beautiful things about Go is that you can create your own interface and any struct that implements it will automatically "implement" it without having to use the implements keyword in java or use the semicolon like in C# to subclass. Why isn't it working with this return type? Am I doing something wrong?

2
  • *sql.Rows doesn't implement IStmt, so you can't return it. Change the interface to return the concrete type (quickest fix). I'd also suggest reading Effective Go and looking at your interface naming (i.e. Queryer or Queryable) and/or reading robots.thoughtbot.com/interface-with-your-database-in-go Commented Jul 12, 2015 at 5:02
  • But *sql.Stmt implements a Query method and QueryRow method just like mine. Query(args ...interface{}) (*Rows, error) and QueryRow(args ...interface{}) *Row. Sure, I use an interface to represent *sql.Rows and *sql.Row, but the method signatures I use are exactly the same. If I change these methods to return *sql.Rows and *sql.Row respectively, it works. If Go can't handle a nested interface from a return type, that's a real shame! Commented Jul 12, 2015 at 6:41

1 Answer 1

3

Here's what I ended up creating to accomplish what I needed. Note that all of this is available in the onedb library that I created at: https://github.com/EndFirstCorp/onedb. In addition to mocking, onedb makes it possible to query Redis and OpenLDAP with the same methods.

import "database/sql"

type rowsScanner interface {
    Columns() ([]string, error)
    Next() bool
    Close() error
    Err() error
    scanner
}

type scanner interface {
    Scan(dest ...interface{}) error
}

type DBer interface {
    Ping() error
    Close() error
    Execute(query string, args ...interface{}) error
    Query(query string, args ...interface{}) (rowsScanner, error)
    QueryRow(query string, args ...interface{}) scanner
}

rowsScanner and scanner are essentially the interfaces for the return of the database/sql Query and QueryRow methods respectively. DBer is ultimately the interface I wish I could get out of database/sql so I could mock it. But, since I can't do that, I created an object which can do the conversion.

type sqllibBackend struct {
    db *sql.DB
    DBer
}

sqllibBackend is the magic struct that does the conversion. It converts the output from *sql.DB methods into the mockable DBer interface. That just leaves the converter struct:

func NewSqllib(driverName, connectionString string) (DBer, error) {
    sqlDb, err := sql.Open(driverName, connectionString)
    if err != nil {
        return nil, err
    }
    err = sqlDb.Ping()
    if err != nil {
        return nil, err
    }
    return &sqllibBackend{db: sqlDb}, nil
}

func (b *sqllibBackend) Close() error {
    return b.db.Close()
}

func (b *sqllibBackend) Query(query string, args ...interface{}) (rowsScanner, error) {
    return b.db.Query(query, args...)
}

func (b *sqllibBackend) QueryRow(query string, args ...interface{}) scanner {
    return b.db.QueryRow(query, args...)
}

func (b *sqllibBackend) Execute(query string, args ...interface{}) error {
    _, err := b.db.Exec(query, args...)
    return err
}

Now, rather than using the real database/sql, I can use sqllibBackend and it returns the easily mockable DBer interface.

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

Comments

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.