golang

Writing tests first, before implementation is easy when our functions don't rely on any external tools or systems. What about unit testing the code that is responsible for interactions with the database? This requires some additional effort, but it pays off big time later on.

Testing Databases

Writing unit tests is based on verifying the behavior of a single part of your application. For example, if you want to test your a function that rounds up numbers, you always know that passing 2.77 in will produce 3 as its outcome. With code that accesses the database, things are different. First of all, it requires some connection to an external entity which might not exist. This is a sign of a serious problem - you can't unit test that code, because tests' success rely on something more than that one piece of the system.

This is why with DBs we generally write integration tests, which rely on two or more elements being present and they check the way they interact with each other. For example, a prerequisite for such a test is that the database exists, is running and has some data stored. The thing is, what if you want to test an API handler that returns data from the DB? You can't check it with unit tests as well? Actually, we can introduce another level of abstractions, called eg. data sources, which we can plug in with custom implementation (not relying on anything external).

Data source as a property

The first step to add a database to our application would be adding a Data Source interface. We should have an ability to access it from each API handler, so that it should probably be a property of API object. Let's turn our single handler function into an API then:

// api/api.go
...
type API struct {
    DataSource db.DataSource
}

func (api API) GetCountries(rw http.ResponseWriter, req *http.Request) {
    c := []Country{
        ...
    }
    json.NewEncoder(rw).Encode(&c)
}

asgashdbas

Then, we obviously need to define what DataSource is. We would require our source to return a list of countries:

// db/datasource.go
type DataSource interface {
    Countries() ([]Country, error)
}

Now we can update our handler to use the source when returning a list of objects:

// api/api.go
...
func (api API) GetCountries(rw http.ResponseWriter, req *http.Request) {
    c, _ := api.DataSource.Countries()

    json.NewEncoder(rw).Encode(&c)
}

asgashdbas

Mock Data Source (for tests)

If we try to run the tests now, we need to update init(..) function:

func init() {
    api := API{}
    http.HandleFunc("/countries", api.GetCountries)
}

but we are doomed to fail:

$ go test .
?       github.com/slomek/playground/mongo    [no test files]
--- FAIL: TestGetCountries (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x476351]

This is because out test implementation doesn't have any data source! Let's create one that will fulfill DataSource interface:

// test/db.go
...
type FakeDataSource struct{}

func (FakeDataSource) Countries() ([]db.Country, error) {
    c := []db.Country{
        db.Country{Code: "PL", Name: "Poland", Capital: "Warsaw"},
        db.Country{Code: "USA", Name: "USA", Capital: "Washington"},
    }

    return c, nil
}

Now running test looks much better:

$ go test ./...
?       github.com/slomek/playground/mongo    [no test files]
ok      github.com/slomek/playground/mongo/api    0.002s
?       github.com/slomek/playground/mongo/db    [no test files]
?       github.com/slomek/playground/mongo/test    [no test files]

But what about production code?

Real Data Source (for production)

Our appliation will be using Mongo database, which we've already covered some time ago. We should start with creating a new DataSource with a constructor, which will take database URI and database name as parameters:

type Mongo struct {
    session *mgo.Session
    dbName  string
}

func NewMongo(dbURI, dbName string) (Mongo, error) {
    session, err := mgo.Dial(dbURI)
    return Mongo{
        session: session,
        dbName:  dbName,
    }, err
}

asgashdbas

Now we need to implement one missing function responsible for returning all Countries stored in Mongo:

func (m Mongo) db() *mgo.Database {
    session := m.session.Copy()
    return session.DB(m.dbName)
}

func (m Mongo) Countries() ([]Country, error) {
    var cs []Country
    err := m.db().C("countries").Find(bson.M{}).All(&cs)
    return cs, err
}

asgashdbas

Note It's popular with Golang and MongoDB to store a pointer to the session as a property, then copy it every time we want to make some interaction. Then we select a database and collection. For convenience we create a utility function that returns a *mgo.Database object from that copied session.

Summary

As you could see, writing tests for database-related code is a bit trickier than doing it for a regular code. On the other hand, you need to worry about the architecture just once, because from now on you are producing testable, well-organized code. In my opinion having the comfort of writing quality unit tests is more than worth the effort.