View Source Mneme (Mneme v0.10.0)

Augments ExUnit.Assertions with a set of assertions that know how to update themselves.

To learn more about how Mneme's auto-assertions are updated, see the pattern generation guide.

use Mneme

When you use Mneme in a test module, assertions are imported and module attributes are made available for configuration.

Configuration

Mneme supports a variety of flexible configuration options that can be applied at multiple levels of granularity, from your entire test suite down to an individual test. While the default behavior will work well for the majority of cases, it's worth knowing which levers and knobs you have available to tweak Mneme to fit your individual workflow.

Options

  • :action (:prompt | :accept | :reject) - The action to be taken when an auto-assertion updates. If CI=true is set in environment variables, the action will always be :reject. The default value is :prompt.

  • :default_pattern (:infer | :first | :last) - The default pattern to be selected if prompted to update an assertion. The default value is :infer.

  • :diff (:text | :semantic) - Controls the diff engine used to display changes when an auto- assertion updates. If :semantic, uses a custom diff engine to highlight only meaningful changes in the value. If :text, uses the Myers Difference algorithm to highlight all changes in text. The default value is :semantic.

  • :diff_style (:side_by_side | :stacked) - Controls how diffs are rendered when the :diff option is set to :semantic. If :side_by_side, old and new code will be rendered side-by-side if the terminal has sufficient space. If :stacked, old and new code will be rendered one on top of the other. The default value is :side_by_side.

  • :force_update (boolean/0) - Setting to true will force auto-assertions to update even when they would otherwise succeed. This can be especially helpful when adding new keys to maps or structs since a pattern like %{} would not normally prompt as the match still succeeds. The default value is false.

  • :target (:mneme | :ex_unit) - The target output for auto-assertions. If :mneme, the expression will remain an auto-assertion. If :ex_unit, the expression will be rewritten as an ExUnit assertion. The default value is :mneme.

Configuring Mneme

There are four ways you can apply configuration options; Each is more specific than the last and will override any conflicting options that were set prior.

  • When calling Mneme.start/1, which will apply to the entire test run;
  • When calling use Mneme, which will apply to all tests in that module;
  • In a @mneme_describe module attribute, which will apply to all tests that follow in the given ExUnit.Case.describe/2 block;
  • In a @mneme module attribute, which will apply only to the next test that follows.

For instance, when an auto-assertion has multiple possible patterns available, Mneme will try to infer the best one to show you first. If you always want the last (and usually most complex) generated pattern, you could call Mneme.start/1 like this:

# test/test_helper.exs
ExUnit.start()
Mneme.start(default_pattern: :last)

As mentioned above, this can be overriden at the module-level, in a describe block, or for an individual test:

defmodule MyTest do
  use ExUnit.Case
  use Mneme, default_pattern: :infer

  test "the default pattern will exclude :baz when this runs" do
    map = %{foo: :one, baz: :three}
    auto_assert %{foo: 1, bar: 2} <- Map.put(map, bar: :two)
  end

  describe "..." do
    @mneme_describe action: :reject

    test "fails without prompting" do
      auto_assert :wrong <- MyModule.some_fun()
    end

    @mneme action: :prompt
    test "prompts to update" do
      auto_assert :wrong <- MyModule.another_fun()
    end
  end
end

Breaking up with Mneme? While the official stance of the library is that this is Not Recommended™, Mneme can even convert all of your existing auto-assertions to regular assertions:

Mneme.start(
  target: :ex_unit,
  force_update: true,
  action: :accept
)

Probably don't do this, but if you do, make sure all your tests are committed first in case you want to get back together.

Summary

Setup

Start Mneme, allowing auto-assertions to run as they appear in tests.

Setup

Start Mneme, allowing auto-assertions to run as they appear in tests.

This will almost always be added to your test/test_helper.exs, just below the call to ExUnit.start():

# test/test_helper.exs
ExUnit.start()
Mneme.start()

Options

  • :action (:prompt | :accept | :reject) - The action to be taken when an auto-assertion updates. If CI=true is set in environment variables, the action will always be :reject. The default value is :prompt.

  • :default_pattern (:infer | :first | :last) - The default pattern to be selected if prompted to update an assertion. The default value is :infer.

  • :diff (:text | :semantic) - Controls the diff engine used to display changes when an auto- assertion updates. If :semantic, uses a custom diff engine to highlight only meaningful changes in the value. If :text, uses the Myers Difference algorithm to highlight all changes in text. The default value is :semantic.

  • :diff_style (:side_by_side | :stacked) - Controls how diffs are rendered when the :diff option is set to :semantic. If :side_by_side, old and new code will be rendered side-by-side if the terminal has sufficient space. If :stacked, old and new code will be rendered one on top of the other. The default value is :side_by_side.

  • :force_update (boolean/0) - Setting to true will force auto-assertions to update even when they would otherwise succeed. This can be especially helpful when adding new keys to maps or structs since a pattern like %{} would not normally prompt as the match still succeeds. The default value is false.

  • :target (:mneme | :ex_unit) - The target output for auto-assertions. If :mneme, the expression will remain an auto-assertion. If :ex_unit, the expression will be rewritten as an ExUnit assertion. The default value is :mneme.

For more information about configuring Mneme, see the Configuration section of the module docs.

Assertions

Link to this macro

pattern <- expression

View Source (macro)

Pattern-match operator.

This operator can only be used in auto_assert/1 or other special forms that support it, like for/1.

While it is similar to the match operator =/2, there are a few differences:

  • It can be used to match falsy values. For instance, the following ExUnit assertion using =/2 will always fail, whereas the auto-assertion using <- will not:

    # fails
    assert false = false
    
    # succeeds
    auto_assert false <- false
  • It supports guards on the pattern with a when clause

    auto_assert pid when is_pid(pid) <- self()
Link to this macro

auto_assert(expression)

View Source (macro)

Pattern-generating variant of ExUnit.Assertions.assert/1.

See also:

See the

Usage

auto_assert generates assertions when tests run, issuing a terminal prompt before making any changes (unless configured otherwise).

auto_assert [1, 2] ++ [3, 4]

# after running the test and accepting the change
auto_assert [1, 2, 3, 4] <- [1, 2] ++ [3, 4]

If the match no longer succeeds, you'll be prompted to update it to the new value.

auto_assert [1, 2, 3, 4] <- [1, 2] ++ [:a, :b]

# after running the test and accepting the change
auto_assert [1, 2, :a, :b] <- [1, 2] ++ [:a, :b]

You're only prompted if the pattern doesn't match. This means you can manually change patterns to ignore parts of the structure you don't care about, and the assertion will still succeed.

# this assertion succeeds, so no prompt is issued
auto_assert [1, 2, | _] <- [1, 2] ++ [:a, :b]
Link to this macro

auto_assert_raise(function)

View Source (since 0.3.0) (macro)

See auto_assert_raise/3.

Link to this macro

auto_assert_raise(exception, function)

View Source (since 0.3.0) (macro)

See auto_assert_raise/3.

Link to this macro

auto_assert_raise(exception, message, function)

View Source (since 0.3.0) (macro)

Pattern-generating variant of ExUnit.Assertions.assert_raise/3.

If the given function does not raise, the assertion will fail.

Like auto_assert/1, you will be prompted to automatically update the assertion if the raised raised exception changes.

Examples

You can pass an anonymous function that takes no arguments and is expected to raise an exception.

auto_assert_raise fn ->
  some_call_expected_to_raise()
end

# after running the test and accepting changes
auto_assert_raise Some.Exception, fn ->
  some_call_expected_to_raise()
end

# optionally include the message
auto_assert_raise Some.Exception, "perhaps with a message", fn ->
  some_call_expected_to_raise()
end

A captured function of arity zero can also be used.

auto_assert_raise &some_call_expected_to_raise/0

# after running the test and accepting changes
auto_assert_raise Some.Exception, &some_call_expected_to_raise/0
Link to this macro

auto_assert_receive()

View Source (since 0.3.0) (macro)

See auto_assert_receive/2.

Link to this macro

auto_assert_receive(pattern)

View Source (since 0.3.0) (macro)

See auto_assert_receive/2.

Link to this macro

auto_assert_receive(pattern, timeout)

View Source (since 0.3.0) (macro)

Pattern-generating variant of ExUnit.Assertions.assert_receive/3.

timeout is in milliseconds and defaults to 100.

Examples

Process.send_after(self(), {:some, :message}, 50)

auto_assert_receive()

# after running the test, messages appearing within 100ms
# will be available as options
auto_assert_receive {:some, :message}

A custom timeout can be specified as a second argument.

Process.send_after(self(), {:some, :message}, 150)

auto_assert_receive nil, 300

# messages appearing within 300ms will now appear as options
auto_assert_receive {:some, :message}, 300
Link to this macro

auto_assert_received()

View Source (since 0.3.0) (macro)

See auto_assert_received/1.

Link to this macro

auto_assert_received(pattern)

View Source (since 0.3.0) (macro)

Pattern-generating variant of ExUnit.Assertions.assert_received/2.

Similar to auto_assert_receive/2, except that the timeout is set to 0, so the expected message must already be in the current process' mailbox.

Examples

send(self(), {:some, :message})

auto_assert_received()

# after running the test, messages in the current process
# inbox will be available as options
auto_assert_receive {:some, :message}