Interface With Your Database in Go Tests

This post originally published on thoughtbot's Giant Robots blog.

Go’s strong type system includes a concept called interface. In Go, an interface is a collection of function signatures. Any type that implements all of those functions is implicitly able to be used as an interface type.

For example, Go’s empty interface, interface{}, is similar to Ruby’s BasicObject or Java’s Object, from which all classes inherit in these languages.

More concretely, Ruby’s Comparable requires a #<=> method to be defined. To represent that in Go, we might define these interfaces as:

type Comparable interface {
  // Compare accepts another Comparable of the same type and returns an int
  // value representing whether the other is less than (-1), greater than (1),
  // or equal to (0) the callee.
  // Compare returns an error if the Comparable argument is of a different type
  // than the callee and cannot be compared.
  Compare(Comparable) (int, error)
}

We could then use any type that implemented Comparable in methods defined to operate on Comparable:

func (self int) Compare(other Comparable) (int, error) {
  switch other.(type) {
  case int:
  case float64:
    other = int(other)
  case string:
    other, err = strconv.Atoi(other)
    if err != nil {
      return 0, err
    }
  default:
    return 0, fmt.Errorf("%v is not comparable to type int.")
  }

  if other > self {
    return 1, nil
  } else if other == self {
    return 0, nil
  } else {
    return -1, nil
  }
}

func Equal(first, second Comparable) (bool, err) {
  if result, err := first.Compare(second); err != nil {
    return false, err
  }

  return result == 0, nil
}

Okay, that’s out of the way. Now let’s look at how we can use interfaces to abstract away a database in tests.

We’ll define a DataAccessLayer interface which includes signatures for the methods we’ll need:

type DataAccessLayer interface {
  FindAuthor(int) Author
  FindPostsForAuthor(Author) []Post
  FindCommentsForPost(Post) []Comment
}

Building to that interface, we’d define a type postgres that included code to retreive each object or collection from a database connection it maintains internally.

But for our unit tests, we don’t want to ever touch the database. By the power of interface, we can define a dummy TestDAL that returns known values so that we can test DataAccessLayer’s collaborators in relative isolation.

type TestDAL {
  Author   Author
  Posts    []Post
  Comments []Comment
}

func (t *TestDAL) FindAuthor(int) Author {
  return t.Author
}

func (t *TestDAL) FindPostsForAuthor(Author) []Post {
  return t.Posts
}

func (t *TestDAL) FindCommentsForPost(Post) []Comment {
  return t.Comments
}

We ignore the arguments because we initialize the TestDAL with the result we want, so they’re not needed.

In our test, we can create just the amount of data we need:

func TestDALCollaborator(t *testing.T) {
  dal := TestDAL{Author: Author{}}
  collaborator := Collaborator{DAL: dal}

  result := collaborator.FunctionNeedingAnAuthor()

  // Some verification
}

And we never need to hit an external dependency like the Postgres database in our test, keeping it blazing fast.