Selfie mascot

selfie

snapshot testing for

Which is
literal, lensable
and like a filesystem

get started

Snapshot testing is the
fastest and most precise
mechanism to record
and specify
the
behavior of your
system and its
components
.

Robots are
writing their
own code. Are
you still writing
assertions by
hand?


is literal  

jvm
pyjs...

This is a reasonable way to test.

@Test
public void primesBelow100() {
  Assertions.assertThat(primesBelow(100)).startsWith(2, 3, 5, 7).endsWith(89, 97);
}

But oftentimes a more useful way to test is actually:

@Test
public void testMcTestFace() {
  System.out.println(primesBelow(100));
}

With literal snapshots, you can println directly into your testcode, combining the speed and freedom of println with the repeatability and collaborative spirit of conventional assertions.

@Test
public void primesBelow100() {
  expectSelfie(primesBelow(100).toString()).toBe_TODO();
}

When you run the test, selfie will automatically rewrite _TODO() into whatever it turned out to be.

@Test
public void primesBelow100() {
  expectSelfie(primesBelow(100).toString())
    .toBe("[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]");
}

And from now on it's a proper assertion, but you didn't have to spend any time writing it. It's not only less work, but also more complete than the usual .startsWith().endsWith() rigamarole.


is like a filesystem  

jvm
pyjs...

That primesBelow(100) snapshot above is almost too long. Something bigger, such as primesBelow(10_000) is definitely too big. To handle this, selfie lets you put your snapshots on disk.

@Test
public void gzipFavicon() {
    expectSelfie(get("/favicon.ico", ContentEncoding.GZIP)).toMatchDisk();
}

@Test
public void orderFlow() {
  expectSelfie(get("/orders")).toMatchDisk("initial");
  postOrder();
  expectSelfie(get("/orders")).toMatchDisk("ordered");
}

This will generate a snapshot file like so:

╔═ gzipFavicon ═╗ base64 length 12 bytes
Umlja1JvbGwuanBn
╔═ orderFlow/initial ═╗
<html><body>
  <button>Submit order</button>
</body></html>
╔═ orderFlow/ordered ═╗
<html><body>
  <p>Thanks for your business!</p>
  <details>
    <summary>Order information</summary>
    <p>Tracking #ABC123</p>
  </details>
</body></html>

Selfie's snapshot files .ss are simple to parse, just split them up on \n╔═. Escaping rules only come into play if the content you are escaping has lines that start with , and you can always use selfie-lib as a parser if you want.

You can treat your snapshot files as an output deliverable of your code, and use them as an input to other tooling.


is lensable  

jvm
pyjs...

A problem with the snapshots we've shown so far is that they are one dimensional. What about headers and cookies? What about the content the user actually sees, without all the markup? What if we could do this?

╔═ orderFlow/initial [md] ═╗
Submit order
╔═ orderFlow/ordered [md] ═╗
Thanks for your business!

Well, you can! Every snapshot has a subject, which is the main thing you are recording. And that subject can have any number of facets, which are named views of the subject from a different lens.

var html = "<html>..."
var snapshot = Snapshot.of(html).plusFacet("md", HtmlToMdParser.parse(html))
expectSelfie(snapshot).toMatchDisk()

You can also use facets in combination with disk and inline literal snapshots to make your tests more like a story.

@Test
public void orderFlow() {
  expectSelfie(get("/orders")).toMatchDisk("initial")
    .facet("md").toBe("Submit order");
  postOrder();
  expectSelfie(get("/orders")).toMatchDisk("ordered")
    .facet("md").toBe("Thanks for your business!");
}

Selfie's faceting is built around Camera, Lens, and Snapshot, whose API is roughly:

final class Snapshot {
  final SnapshotValue subject;
  final ImmutableSortedMap<String, SnapshotValue> facets;
}
interface Lens {
  Snapshot transform(Snapshot snapshot);
}
interface Camera<T> {
  Snapshot snapshot(T subject);
  default Camera<T> withLens(Lens lens) {
    // returns a new Camera which applies the given lens to every snapshot
  }
}

See the facets section for more details on how you can use Selfie for snapshot testing with Java, Kotlin, or any JVM language.


is cacheable  

jvm
pyjs...

Sometimes a test has a component which is slow, expensive, or non-deterministic. In cases like this, it can be useful to save the result of a previous execution of the API call, and use that as a mock for future tests.

var client = ExpensiveAiService();
var chatResponse = cacheSelfie(() -> {
  return client.chat("What's your favorite number today?");
}).toBe("Since it's March 14, my favorite number is π")
// build other stuff with the chat response

You can cache simple strings, but you can also cache typed API objects, binary data, or anything else you can serialize to a string or a byte array.

var imageBytes = cacheSelfieBinary(() -> {
  return client.generateImage("A robot making a self portrait");
}).toBeFile("selfie.png")

For more information on how to use cacheSelfie, see the cache example.

are you still writing assertionsby hand?
let your codebase take its own selfiesget started