JVM snapshot tests with selfie

✦ 2025-11-23

I’m a big fan of snapshot testing. Snapshots are great to avoid manually writing a bunch of assertions. You just pass in the actual object and let the snapshot library produce the expected result. With traditional assertions, your tests are only as good as the fields you assert against. But snapshot tests tend to capture _everything_or at least a lot more than a human writing assertions would. So you often catch issues in fields that you wouldn’t ordinarily.

I’ve used Jest’s snapshot tests for years. And now there’s a way to run snapshot tests on the JVM 🙌. All thanks to selfie.

🗒️ Tests performed using JDK21 (Corretto), Kotlin v2.2.20, and selfie-runner-junit5 v2.5.0.

Inline snapshots

My favourite kind of snapshot tests are inline ones. I love grouping the snapshots with the code making the assertion. Selfie supports these just fine.

Disk snapshots

Disk snapshots work exactly as you’d expect.

Updating snapshots

As you iterate on your code, you’ll need to update your existing snapshots to match new expectations. Selfie provides a bunch of different ways to do this.

Delete the snapshot and add _TODO back

The is the most basic way of updating snapshots. You just delete the existing snapshot and add _TODO back to the tests. But this will quickly become laborious and time consuming.

So you turn this:

@Test
fun `test inline snapshot`() {
    Selfie.expectSelfie(listOf(1, 2, 3).toString()).toBe("[1, 2, 3]")
}

back in to this:

@Test
fun `test inline snapshot`() {
    Selfie.expectSelfie(listOf(1, 2, 3).toString()).toBe_TODO()
}

And then re-run the tests.

Via the selfie/SELFIE environment variable / system propety.

Probably my favourite method is to just re-run the test suite and tell selfie to update all snapshots.

./gradlew test -Pselfie=overwrite
# or
SELFIE=overwrite ./gradlew test

I like this method because it matches how I update Jest snapshots (npm run test -- --updateSnapshot). You can use your version control to see which snapshots have changed and revert the snapshots back if they don’t look right.

With the //selfieonce magic comment

Adding this comment into the test file will cause selfie to overwrite the snapshots in the file and then remove the comment.

With the //SELFIEWRITE magic comment

The same as above but the comment will stay there. Subsequent test runs will overwrite the snapshots.

Use SelfieSettingsAPI to write custom formatters

In much the same way that AssertJ lets your write custom assertions, selfie lets you write custom snapshot serializers. In the following example, I’ve written a serializer that makes string lists look like markdown.

object CustomSelfie : SelfieSettingsAPI() {
    fun expectSelfie(response: List<String>): StringSelfie {
        return expectSelfie(response) {
            Snapshot.of(it.joinToString(
                prefix = "\n", 
                separator = "\n", 
                postfix = "\n"
            ) { item -> "- $item" })
        }
    }
}