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.