Java, Programming

Stop Using the Database in your Unit Tests

Hi everybody – we’ve been having this conversation for 20 years already, but I stumbled across a bit of code today, and I just have to say it again: Please don’t use the database in your unit test unless there is a good reason.

Everybody stay calm. I’m not saying that you should never use a database in any of your tests. I’m just saying that your default should be to do the test without the use of a database.

I can give you lots of reasons why, but today, we are just going to talk about speed. Here’s the code under test – it’s a classic Kotlin one-liner.

fun isValid() = LocalDateTime.now().minusDays(14) < createdAt

Take a look at this test below, courtesy of the team at Testery (and yes, at Testery, we unit test our testing platform!)

It’s a valuable test, and it covers an important business rule – out platform invitations expire at 14 days. I feel okay about sharing this Testery trade secret with my readers – just don’t tell anybody else.

import io.kotlintest.shouldBe
import io.kotlintest.shouldNotBe
import io.kotlintest.specs.StringSpec
import java.time.LocalDateTime

class InvitationTests : StringSpec({

    "not invalid after 13 days" {
        var invite = InvitationRepo.createInvite("test@test.com", "first", "last", "a")
        invite = InvitationRepo.setCreatedAt(invite.id, LocalDateTime.now().minusDays(13))

        invite.isValid() shouldBe true
    }

    "invalid after 14 days" {
        var invite = InvitationRepo.createInvite("test@test.com", "first", "last", "a")
        invite = InvitationRepo.setCreatedAt(invite.id, LocalDateTime.now().minusDays(14))

        invite.isValid() shouldBe false
    }
})

If we run this test file, the average runtime is 646 milliseconds. That doesn’t sound bad, but lets see if we can do better without altering the application code, or changing the intention of the test.

import io.kotlintest.shouldBe
import io.kotlintest.specs.StringSpec
import java.time.LocalDateTime

class InvitationTests : StringSpec({

    "not invalid after 13 days" {
        var invite = Invitation()
        invite.createdAt = LocalDateTime.now().minusDays(13)
        invite.isValid() shouldBe true
    }

    "invalid after 14 days" {
        var invite = Invitation()
        invite.createdAt = LocalDateTime.now().minusDays(14)
        invite.isValid() shouldBe false
    }
})

This runs in an average of 146 milliseconds. That’s a 78% reduction of runtime. And we haven’t lost any test coverage on the unit under test. It never cared about persistence, and the database writes & reads were always superfluous to the business logic.

So, yeah, if you want your unit tests to be 5x faster, stop calling the database on your business logic tests, and save the overhead for critical integration and end-to-end tests.

If you’re having trouble with your testing strategy, get in touch with me and the team at Testery – we can help get your test automation strategy in place.