Mneme - Snapshot testing tool for the busy programmer

Getting closer to the next sizable update to Mneme and wanted to share! After noticing that it can sometimes be difficult to visually parse what changed when an auto-generated value updates, I started working on semantic diffing, using the amazing Difftastic as reference and inspiration.

This has proved more of a rabbit hole than I initially thought, but is still relatively easy thanks to Elixir’s general awesomeness. Today I reached the point where my semantic diff development branch can start using semantic diffs to work on semantic diffs!

For instance, here’s what an update might look like using the latest release:

Here’s the same update using semantic diffs:

image

There’s still a ways to go before this lands, but I’m hoping that being able to use semantic diffs in development will help expose edge-cases/etc. before I ship the feature.

7 Likes

Semantic diffing is now in main. If anyone is interested in trying it out, please add :mneme as a git dependency and enable semantic diffs in your test.exs configuration:

# mix.exs
defp deps do
  [
    {:mneme, github: "zachallaun/mneme", only: :test}
  ]
end

# config/test.exs
config :mneme, defaults: [diff: :semantic]

If you run into any errors, please let me know by opening an issue on GitHub!

2 Likes

v0.2 released

Note: Mneme now requires Elixir v1.14 or later.

defp deps do
  [
    {:mneme, "~> 0.2.1", only: :test}
  ]
end

Semantic diffs

In addition to some quality-of-life changes when things go wrong, the big feature that v0.2 brings is a custom diff engine that produces dramatically more readable diffs when auto-assertions are updated. I’m really excited about this and it’s made Mneme much more pleasant to use, at least for me.

I expect there to be some formatting bugs – please report them on GitHub if you encounter anything weird! If you desire the old behavior, you can configure Mneme to continue using textual diffs as before.

Example 1. New assertions highlight only the new value

Text

Semantic

Example 2. Changed assertions highlight deleted/added nodes

Text

Semantic

Example 3. Slightly changed strings call out the changed portion

Text

Semantic

Future work

  • Diff quality: These diffs could still be improved. For instance, example 2 highlighted a suboptimal set of square brackets.
  • Performance: For large expressions, semantic diffs will take too long and it will fall back to text diffs. I’m sure there’s still some low-hanging fruit for improving performance, however.
  • Formatting: I’d like to add side-by-side formatting to decrease the vertical real-estate needed. I’d also like to explore replacing very large, unchanged nodes with ... or something in the same way that inspect does at a certain point.
4 Likes

I’ve released a new version (v0.2.2) that disables an optimization that was causing a poor diff in example 2 above, increasing the likelihood that subtrees would become “misaligned”.

v0.2.1

v0.2.2

The new diff does a much better job of reflecting the actual change: the nil is being replaced with a new subtree, and the second subtree is being modified.

5 Likes

I’ve released v0.2.3 which includes fixes for a few bugs.


I’d also like to mention a proposal I just posted on GitHub for the introduction of mix mneme to run tests with Mneme prompts, which would be favored over the current behavior that hijacks mix test. If you have any thoughts/feelings about this, I’d invite you to read the proposal and react/respond on GitHub. :slightly_smiling_face:

v0.2.6 released

defp deps do
  [
    {:mneme, "~> 0.2.6", only: :test}
  ]
end

Changes

In addition to a number of bug fixes, this release updated (and hopefully improved) the formatting for semantic diffs, including the addition of side-by-side diffs if terminal width allows. (This can be disabled with a config option if you prefer stacked diffs.)

An option to “skip” a changed assertion has also been added. This will pass the test, but fail the test run at the end. This can be useful if a test contains multiple auto-assertions, as rejecting the first one would immediately fail that test and prevent the later ones from running.

New demo:

2 Likes