Skip to content

About

Inline snapshot tests for Kotlin/Java

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Latest commit

 

History

131 Commits

Folders and files

Repository files navigation

ci-badge latest release latest release date

Expekt tests

Inline snapshot testing for Kotlin and Java, inspired by JaneStreet's expect-tests framework for OCaml

waveforms demo

Principles

Expekt is a lightweight inline snapshot testing tool:

  • targeted toward to the assert/then part of a typical arrange-act-assert/given-when-then test setup
  • compare textual representation of expected (snapshoted) outputs and actual ones. With Expekt, the expected outputs are expressed as raw string blocks in source code (the inline part).
  • offers a 'promotion' mechanism to replace the expected outputs with the actual ones when the user (developer) decides the change is suitable. The promotion is done directly in the source code, altering the string blocks.

This means that efforts to represent your objects as readable strings, (such as ascii art, tables, graphs ...) can be directly exploited as readable assertions. It also makes updating these assertions effortless, should their representation change.

Alternatives and external resources on snapshot testing

Snapshot testing (also called approval, acceptance, golden-master, ...) is a popular technique in many ecosystems.

A few resources on this topic:

Some alternatives or equivalents in other ecosystems:

Here is a quick comparison table if you want to choose between Expekt and another JVM tool, or come from another language and want an idea of the corresponding features in Expekt:

Tool JVM Inline snapshot File snapshot Update control Interactive snapshot review Extensible diff/review
Expekt ✅ ✅ ❌ user-configurable flag, per test class, test method, or promote@ label on a snapshot ✅ ✅
Selfie ✅ ✅ ✅ global flag, toBe_TODO() or //selfieonce comment ❌ ❌
ApprovalTests ✅ ❌ ✅ interactive, global flag ✅ ✅
expect-test ❌ ✅ ❌ promotion command to update differing snapshots ❌ ❌
Jest ❌ ✅ ✅ interactive, promotion command to update differing snapshots ✅ ❌
Insta ❌ ✅ ✅ interactive, global flag ✅ ❌
Verify ❌ ❌ ✅ interactive, global flag ✅ ✅

Setup

From Maven

Add expekt dependency to your pom.xml:

<dependency>
    <groupId>io.github.nyub</groupId>
    <artifactId>expekt-test</artifactId>
    <version>1.1.0</version>
    <scope>test</scope>
</dependency>

From source

The source code is only comprised of 2 files (and even 1 if you don't use the JUnit extension). Feel free to download ExpectTests.kt and ExpectTestExtension.kt directly into your sources and experiment.

Usage

Kotlin

The recommended usage for Kotlin is to define an ExpectTests shared configuration and write your test with the expectTest { } scope function. See the kotlin tests for setup examples.

Promotion is triggered:

"<CONTENT>".expect(promote@"""
<CONTENT>
""")

The JUnit 5 extension is also usable from the Kotlin side, even if it brings fewer improvements than for Java users.

Java

The recommended usage for Java is to use the provided JUnit 5 extension. See the java tests for examples.

Promotion is triggered:

  • By setting the system property nyub.expekt.promote to "true"
  • At class level by using @Promote(true/false) annotation
  • At method (test) level by using @Promote(true/false) annotation

For non-junit codebases, the Kotlin scope functions are usable on the Java side, with slightly degraded ergonomics.

Examples

More examples are available in the demo folder

Constraints on expected string blocks

Expekt detects string blocks to check and promote based on a few heuristics.

This imposes some formatting rule regarding the expect( call.

  1. the expect( call should be written in place, not aliased
  2. the expected content
  3. the opening triple-quotes should be the next token after the expect( call (not nested in parentheses or aliased)
  4. the closing triple-quotes
    • should be on a different line than the opening triple-quotes
    • should be on a different line than the expected content

OK:

expect("""
<CONTENT>
""")
expect(
"""
<CONTENT>
""")
expect(
"""
<CONTENT>
"""
)

Not OK:

expect("<CONTENT>")
  • (because the expected content is not within a triple-quoted block)
expect("""<CONTENT>""")
  • (because the closing quotes are on the same line as the opening quotes)
expect("""
<CONTENT>""")
  • (because the closing quotes are on the same line as the expected content)
expect(f("""
<CONTENT>
"""))
  • (because the opening quotes are not the next token after the expect( call)

        fun alias(s: String) = expect(s)
        alias(
        """
           <CONTENT>
           """
        )

snippet source | anchor

  • (because expect is aliased, so the search starts from the actual call site on the first line)
val content = "<CONTENT>"
expect("""
$content
""")
  • (because the expected string block uses string interpolation)

See ExpectTestsTest.ExpectCallConstraintsTest for more invalid examples

Customizing promotion review

When comparing actual and expected contents, ExpectTests.promote() is called to choose between updating the expected content or perform equality assertion, where promote is an implementation of PromotionTrigger. Injecting an implementation of PromotionTrigger to ExpectTests configuration allows to define any custom review mechanism, for example interactive prompting or reports.

The builtin PromptWithDiffPanel pops up a git-like diff view asking user to accept or not each differing snapshot along the test suite.

Interactive review gif

About

Inline snapshot tests for Kotlin/Java

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages