I have written a simple package, which basically consists of a lot of getter functions. Each file in this package corresponds to a service, so for instance the product file, contains functions relating to the product service/db, order file to order service etc. Each function takes as parameters a db resource to the underlying db, and parameters for the sql, eg. productid, name, orderid. Each of the functions returns a struct (eg. Order, product) or an error:
// product.go
package lib
type Product struct {
ID int
Name string
Price float
}
func GetProductById(DB *sql.DB, ID int) (p Product, err error) {
q := "SELECT * FROM product WHERE id = " + ID
...
}
func GetProductByName(DB *sql.DB, name string) (p Product, err error) {
...
}
// order.go
package lib
type Order struct {
ID int
Date string
Items []items
}
func GetOrderById(DB *sql.DB, ID int) (o Order, err error) {
...
}
The problem is that I'm not able to mock these functions from my main package. What I really like to do, is to rewrite the package, so I somehow can pass to function to a type instead. But I'm not sure how to do this. Especially not when the functions take different input parameters and return different structs. Is there a way to do this?
In Go you can NOT mock a function, or a method declared on a concrete type.
For example:
func F()
func (T) M()
F
and M
are not mockable in Go.
However you can mock function values, whether they are variables, fields on a struct, or parameters passed to other functions.
For example:
var Fn = func() { ... }
type S struct {
Fn func()
}
func F(Fn func())
Fn
in all three cases is mockable.
The other thing that you can mock in Go, and the prefered option most of the time, is an interface
.
For example:
type ProductRepository interface {
GetProductById(DB *sql.DB, ID int) (p Product, err error)
}
// the real implementater of the interface
type ProductStore struct{}
func (ProductStore) GetProductById(DB *sql.DB, ID int) (p Product, err error) {
q := "SELECT * FROM product WHERE id = " + ID
// ...
}
// the mock implementer
type ProductRepositoryMock struct {}
func (ProductRepositoryMock) GetProductById(DB *sql.DB, ID int) (p Product, err error) {
// ...
}
Now any piece of code that depends on ProductRepository
can be passed a value of type ProductStore
when you're in "normal mode" and a value of type ProductRepositoryMock
when you're testing.
Another option using interface
s, which allows you to keep your functions mostly unchaged is to define an interface that mimics the methods of *sql.DB
then use that interface type as the type to be passed to your functions, implement a mock version of that interface and use that during testing.
For example:
type DBIface interface {
Query(query string, args ...interface{}) (*sql.Rows, error)
// ...
// It's enough to implement only those methods that
// the functions that depend on DBIface actually use.
// If none of your functions ever calls SetConnMaxLifetime
// you don't need to declare that method on the DBIface type.
}
type DBMock struct {}
func (DBMock) Query(query string, args ...interface{}) (*sql.Rows, error) {
// ...
}
func GetProductByName(DB DBIface, name string) (p Product, err error) {
...
}
DB
parameter to GetProductByName
is now mockable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With