Este post también está disponible en español.

When we write our tests we usually use real examples of the desired behavior so that they are easy to understand.

@Test
fun `cart total is the sum of all added products`() {
    val shampoo = Product("Shampoo", Money("$50"))
    val soap = Product("Soap", Money("$5"))
    
    cart.add(shampoo)
    cart.add(soap)
    
    assertThat(cart.total).isEqualTo(Money("$55"))
}

When the objects are complex, creating valid instances with different data becomes a problem for clarity (details are mixed with important stuff) and also are tedious to write.

val alice = Customer("Alice", "Brown", ContactInfo(Address("123 Main Street", "New York", "NY 10030")))
val bob = Customer("Bob", "Davies", ContactInfo(Address("456 Silver Street", "Seattle", "WA 98126")))

It will be great if we can have valid examples easily.

val alice = Customer("Alice", "Brown", contactInfos.one())
val bob = Customer("Bob", "Davies", contactInfos.one())

We need to take care of the testing code as much as we do with our production code. Both need to be maintained, so the same software design principles apply.

Test code should reveal intention, be easy to understand, they shouldn’t have duplication of knowledge, etc.

As the software grows we usually end up with some objects dedicated to assist us with the creation of examples and testing scenarios. It should be easy for us to write new tests and be agile while doing TDD.

In this post I will show you a little utility class to provide examples for our tests.

Examples

We’ll like to have common examples quickly and easily. For example:

// This will give us a valid example of an email, e.g., "alice@gmail.com".
val email = emails.one()
// This should be different from the previous example, e.g., "bob@gmail.com"
val otherEmail = emails.one()

// We can have examples of different types 
val aDate = dates.one()
val aName = names.one()
val aPhone = phones.one()

// Including our value objects
val aCountry = countries.one()
val anAddress = addresses.one()

Our little utility class should be easy to use. It should be easy to build our own examples.

Here’s how the examples are built with our utility class:

val firstnames = TestExamples("Alice", "Bob", "Charlie", "David", "Erin", "Franck", "Grace", "Heidi")
val lastnames = TestExamples("Brown", "Johnson", "McCartney", "Borg", "Davies")   

// We can build examples dynamically by using other examples
val fullnames = TestExamples(List(30) { names.one() + " " + lastnames.one() })

// We can also use our own value objects
val emails = TestExamples(listOf("alice@gmail.com", "bob@gmail.com").map { Email(it) })
val addresses = TestExamples(List(20) { 
    Address(streets.one(), cities.one(), countryCodes.one(), zipCodes.one()) 
})

The TestExamples class

And finally our little utility class…

class TestExamples<T>(examples: List<T>) {
    private var examples = ThreadLocal<List<T>>().apply { set(examples) }
    private var current = -1

    constructor(vararg examples: T): this(examples.toList())

    fun one(): T {
        current++
        return examples.get()[current % examples.get().size]
    }
}

The method one returns a new example each time. If we exhaust all the possible examples it’ll simply start over.

As you can see this little utility class can provide us with great value when we are writing our tests.

I hope you find it as useful as I do, see you next time!