DEV Community

John Forstmeier
John Forstmeier

Posted on • Updated on

Golang Database Mocks

At the heart of our current side project is a MemSQL database that we use as our core data pipeline; it's a really cool piece of technology that's blazingly fast and we love it to death. However, testing this code can be difficult, as I quickly discovered through trial and error (but mostly errors). Since the Go standard packages are thoroughly tested, I only wanted to make sure that the code that was calling and relying on them would function correctly in production.

There are two steps that I took to handle testing in our project's database code.

Starting with the database/sql package we have the sql.DB struct which represents a pool of connections which features a series of methods for interacting with those connections. In our codebase we use two of these (and one function which returns an open database):

func Open(driverName, dataSourceName string) (*DB, error)

func (db *DB) Close() error

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
Enter fullscreen mode Exit fullscreen mode

Following these, I built an interface called sqlDB below:

type sqlDB interface {
    Close() error
    Query(query string, args ...interface{}) (*sql.Rows, error)
Enter fullscreen mode Exit fullscreen mode

This was sort of "moving in reverse" where I defined an interface that was being implemented by the sql.DB struct methods. With this it becomes possible to test the code immediately surrounding the calls to these functions/methods as test struct objects can be used by the unit tests. Note that this still requires a bit of extra setup due to dependencies on things like sql.Rows in the return values but it does bring the coverage much closer to the actual database/sql package.

In addition to this, a second layer of testing was also constructed. A dataAccess interface was defined which included specific methods that would be used to return prepared objects for processing by the rest of the backend. These functions would be calling the sqlDB methods above which further encapsulates the database/sql package from our codebase and unit testing:

type dataAccess interface {
    readIntegrations(query string) (map[int64]*integration, error)
    readSettings(query string) (map[int64]*settings, error)
    readEvents(query string) (map[int64][]*preprocess.Container, error)
Enter fullscreen mode Exit fullscreen mode

Here again, using an interface allows us to swap out even more production code in our testing to see different test cases and scenarios in the calling code.

In short:

  • the sqlDB interface allows us to test code immediately interacting with the database object - testing stuff that process the database query values
  • the dataAccess interfaces lets us unit test post-processed values like the map outputs above - the bulk of the backend logic

There are different ways of conducting unit tests with databases but this is the approach that I took in our Go project - I'm open to suggestions/alternatives and happy unit testing!

Discussion (0)