Rob Archibald
Rob Archibald

Reputation: 397

Mocking database/sql structs in Go

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?

Upvotes: 4

Views: 7800

Answers (1)

Rob Archibald
Rob Archibald

Reputation: 397

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.

Upvotes: 3

Related Questions