[ ].blue

Empty, as a Contract

June 27, 2014

If you have a Scala project and you do any amount of testing in it, it's more than likely you've got some big collection of "factories" that just instantiate models with test values.

Perhaps something like...

1
2
3
4
5
6
// person.scala
case class Person(name: String, age: Option[Int])

// test/factories.scala
def buildPerson(name: String = "Bob", age: Option[Int] = Some(30)) =
  Person(name, age)

This approach works, but it creates a constant maintenance overhead to keep up all of these factories whenever the type signature of a model changes. While not a perfect solution, I noticed that Seq has a function empty on it's companion class. We could think of empty as a kind of contract with a model.

1
2
3
trait Emptyable[A] {
  def empty: A
}

Example implementation:

1
2
3
4
5
case class Person(name: String, age: Option[Int])

object Person extends Emptyable[Person] {
  def empty = Person("", None)
}

Obviously this is less than pure. "" for name isn't exactly robust. However with the empty method in place, it means that we can now write our tests using that function, being guaranteed that we'll always get back whatever that model considers "empty". Using the copy method case class provides, any values that need to be initialized go in there. Where this approach shines is that whenever a model changes (ie. a field gets added), it won't break every test in the world.

1
val testPerson = Person.empty.copy(name = "The Field I Care About")