To start snapshot testing in Python, all you need is to add a single dependency.


Installation  

Requirements  

Selfie requires Python 3.9 or newer. It has plugins for the following Python test runners:

  • Pytest via pytest-selfie (PyPI, changelog)
  • PRs welcome for other test runners (see here for a guide)

Quickstart  

If you haven't seen the GIF on our GitHub, you might want to watch that first (give us a ⭐ while you're at it 😉).

Let's say we have the following test code:

expect_selfie([1, 2, 3]).to_be_TODO()

If you run this test, selfie will rewrite your sourcecode to be this:

expect_selfie([1, 2, 3]).to_be([1, 2, 3])

Now, let's change the code to this:

expect_selfie("oops").to_be([1, 2, 3])

When we run the test, we will get a failure, and the failure message will be:

Snapshot mismatch
- update this snapshot by adding `_TODO` to the function name
- update all snapshots in this file by adding `#selfieonce` or `#SELFIEWRITE`

As you can see, we have three options:

  • replace to_be with to_be_TODO (you can leave or remove the [1, 2, 3], makes no difference)
    • rewrites only this one snapshot, and selfie will remove the _TODO
  • put #selfieonce anywhere in the file
    • rewrites all snapshots in the file, and selfie will remove #selfieonce after it has done so
  • put #SELFIEWRITE anywhere in that file
    • rewrites all snapshots in the file until you remove #SELFIEWRITE yourself

Disk  

To store a snapshot on disk, swap toBe for to_match_disk:

expect_selfie([1, 2, 3]).to_match_disk_TODO()

This will create a file called SomethingOrOther.ss in the same directory as your test. It will also rewrite the test source to:

expect_selfie([1, 2, 3]).to_match_disk()

Just like inline literal snapshots, you can use _TODO, #selfieonce, and #SELFIEWRITE to control how the snapshots are written and updated. You don't have to use _TODO if you have the #selfieonce or #SELFIEWRITE comments in your file.

If you want the disk snapshots to live in a different folder, set snapshotFolderName using SelfieSettings.


CI  

The nice thing about #SELFIEWRITE is that all of your snapshots will update on every run, which makes it easy to explore — like multiassert on steroids. The bad thing about #SELFIEWRITE is that all of the tests always pass, even if the snapshots actually change on every run.

For example, you might have a realtime timestamp or a random port number embedded in a snapshot. Randomness and realtime cannot be present in a repeatable assertion, and you might not realize that a tiny part of a large snapshot is changing while you're in #SELFIEWRITE mode.

For this reason, it is critical that a CI server should always run in readonly mode. No action is needed on your part, selfie automatically puts itself into readonly mode if the CI=true environment variable is present, which is true for all popular CI systems.

When in readonly mode, selfie not only refuses to update any snapshots, it also fails the build if _TODO, #selfieonce, or #SELFIEWRITE are present in the sourcecode, even if the snapshots were correct. Writing snapshots is a strictly private affair 😏.


Overwrite everything  

Selfie has three modes:

  • interactive, the default mode, which we discussed in the quickstart
  • readonly, the default mode if CI=true, where no snapshots can be written
  • overwrite, where every snapshot is overwritten, regardless of whether it is _TODO or not

To set the mode, you set the selfie or SELFIE environment variable or system property to either interactive, readonly, or overwrite. But in the vast majority of cases, it's best to leave it alone and let the defaults do their thing.


Strings, repr, and more  

If you call repr(x) on some variable x, Python will return a string that represents the underlying value. This string is what selfie uses to stuff a value inside the brackets of to_be().

All of the examples so far have asserted on the string returned by repr. But selfie also has special handling for byte arrays (bytes).

expect_selfie(10/4).to_be(2.5)
expect_selfie((10/4) == 2.5).to_be(True)

# saves as base64 within a subsection of a `.ss` file, with garbage collection for unused sections
expect_selfie(bytearray(100)).to_match_disk()

# base64 assertion
expect_selfie(bytearray(100)).to_be_base64("blahblah") 

# raw local file, no garbage collection
expect_selfie(generatePngFile()).to_be_file("you_can_open_image_locally.png") 

But the real power of selfie is asserting on arbitrary objects using facets, which are covered in more detail in their own section.


Reference  

Full API documentation is available at pydoc.selfie.dev.

  • .to_be_TODO() or .to_be_TODO(argumentIsIgnored)
    • creates or updates an inline literal snapshot
  • .to_match_disk_TODO()
    • creates or updates a disk snapshot
  • #selfieonce
    • all snapshots in the file will be updated, whether they are _TODO or not
    • selfie will remove the comment after the snapshots have updated
  • #SELFIEWRITE
    • all snapshots in the file will be updated, whether they are _TODO or not
    • selfie will not remove the comment after the snapshots have updated
  • mode is set by the SELFIE environment variable or system property
    • interactive, default
    • readonly, default if CI environment variable is true
    • overwrite, all snapshots can be overwritten
  • Camera and Lens are covered in the facets section.
  • cache_selfie and binary snapshots (to_be_base64 and to_be_file) are covered in the cache section

Pull requests to improve the landing page and documentation are greatly appreciated, you can find the source code here.