/ˈniːmiː/ - Snapshot testing utilities for Elixir

Hex.pm Docs CI

🎥 Video Demo

https://gist.github.com/assets/503938/3155b333-6a59-448e-8735-dc0093bd677e

Mneme augments ExUnit.Assertions with a set of assertions that know how to update themselves. This is sometimes called snapshot or approval testing.

With Mneme, you write something like...

auto_assert my_function()

...and next time you run your tests, Mneme:

  1. runs the code and generates a pattern from the returned value, then
  2. prints a diff and prompts you to confirm the change.

Now your test looks like this:

auto_assert %MyAwesomeValue{so: :cool} <- my_function()

This lets you quickly write lots of tests, and like ordinary tests, you'll see a diff when they fail. But, unlike ordinary tests, you can choose to accept the changes and Mneme will rewrite the test for you.

Features:

Take a brief tour:

If you'd like to see Mneme in action, you can download and run examples/tour_mneme.exs, a standalone tour that only requires that you have Elixir installed.

$ curl -o tour_mneme.exs https://raw.githubusercontent.com/zachallaun/mneme/main/examples/tour_mneme.exs
$ elixir tour_mneme.exs

Quickstart

  1. Add :mneme do your deps in mix.exs:

    defp deps do
    [
    {:mneme, ">= 0.0.0", only: [:dev, :test]}
    ]
    end
  2. Add :mneme to your :import_deps in .formatter.exs:

    [
    import_deps: [:mneme],
    inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
    ]
  3. Start Mneme right after you start ExUnit in test/test_helper.exs:

    ExUnit.start()
    Mneme.start()
  4. Add use Mneme wherever you use ExUnit.Case:

    defmodule MyTest do
    use ExUnit.Case, async: true
    use Mneme
    test "arithmetic" do
    auto_assert 2 + 2
    end
    end
  5. Run mix test and type y<ENTER> when prompted; your test should look like:

    defmodule MyTest do
    use ExUnit.Case, async: true
    use Mneme
    test "arithmetic" do
    auto_assert 4 <- 2 + 2
    end
    end

Requirements

Elixir

Mneme requires Elixir version 1.14 or later.

Formatter

If you do not use a formatter, the first auto-assertion will reformat the entire file, introducing unrelated formatting changes. Mneme rewrites your test scripts when updating an assertion using the formatter configuration for your project.

It's highly recommended to configure your editor to format Elixir files on-save.

Note: Mneme uses Rewrite to update files, which supports the Elixir formatter and FreedomFormatter.

Command line interface

Auto-assertions are run with your normal tests when you run mix test and a terminal prompt is used whenever one needs to be updated. Here's what that might look like:

Screenshot of Mneme CLI

Whenever that happens, you have a few options:

Key Action Description
y Accept Accept the proposed change. The assertion will be re-run and should pass.
n Reject Reject the proposed change and fail the test.
s Skip Skip this assertion. The test will not fail, but the mix test process will exit with 1.
k Next If multiple patterns have been generated, cycle to the next one.
j Previous If multiple patterns have been generated, cycle to the previous one.

Note that the CLI is not available when tests are run in a CI environment. See the Continuous Integration section for more info.

Generated patterns

Mneme tries to generate match patterns that are close to what you would write. Basic data types like numbers, lists, tuples, and strings will generate as you would expect. For maps and structs, Mneme will often give you a couple of options. For values without a literal representation, like pids, guards will be used.

auto_assert pid when is_pid(pid) <- self()

Additionally, local bindings can be found and pinned as a part of patterns. This keeps the number of "magic values" down and makes tests more robust.

test "create_post/1 creates a new post with valid attrs", %{user: user} do
valid_attrs = %{title: "my_post", author: user}
auto_assert create_post(valid_attrs)
end
# generates:
test "create_post/1 creates a new post with valid attrs", %{user: user} do
valid_attrs = %{title: "my_post", author: user}
auto_assert {:ok, %Post{title: "my_post", author: ^user}} <- create_post(valid_attrs)
end

Special patterns

This is a non-exhaustive list of things Mneme takes into consideration when generating match patterns:

Continuous Integration

In a CI environment, Mneme will not attempt to prompt and update any assertions, but will instead fail any tests that would update. This behavior is enabled by the CI environment variable, which is set by convention by many continuous integration providers.

export CI=true

Editor support

Guides for optional editor integration can be found here:

Acknowledgements

Special thanks to: