Reputation: 397
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
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