Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock functions in golang

Tags:

mocking

go

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?

like image 334
madshov Avatar asked Dec 03 '22 21:12

madshov


1 Answers

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 interfaces, 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.

like image 73
mkopriva Avatar answered Jan 17 '23 21:01

mkopriva