Skip to content
Become a
Sponsor or follow @15r10nk on
or
for updates

# snapshot_arg(...)

`snapshot_arg` lets you embed snapshot assertions inside a helper function, so callers don't need to pass `snapshot()` at every call site. It is an advanced feature for integrating inline-snapshot more deeply into your tests or an existing testing framework.

Consider a helper like this, where callers must pass `snapshot()` explicitly:

    from inline_snapshot import snapshot

    def check(data, min_value, max_value):
        assert min(data) == min_value
        assert max(data) == max_value

    def test():
        check([1, 3], snapshot(), snapshot())
        check([5, 5], snapshot(), snapshot())

With `snapshot_arg` you can move the snapshot call into `check` itself, eliminating the repetition at every call site. Snapshots created by `snapshot_arg()` follow the same rules as those created by `snapshot()`. You can use all the same operators such as `<=` and `in`, and create sub-snapshots with `[]`.

    from inline_snapshot import snapshot_arg

    def check(data, min_value=..., max_value=...):
        assert min(data) == snapshot_arg(min_value)
        assert max(data) == snapshot_arg(max_value)

    def test_numbers():
        check([1, 3])
        check([5, 5])

The `...` acts as a placeholder default, so callers don't need to pass a value — inline-snapshot fills one in when you run `pytest`.

    from inline_snapshot import snapshot_arg

    def check(data, min_value=..., max_value=...):
        assert min(data) == snapshot_arg(min_value)
        assert max(data) == snapshot_arg(max_value)

    def test_numbers():
        check([1, 3], min_value=1, max_value=3)
        check([5, 5], min_value=5, max_value=5)

`snapshot_arg` also supports real default values: inline-snapshot omits an argument at the call site when its current value matches the default.

A default of `...` uses the create category to generate the initial value. Any other default uses fix, and the argument is only written if the actual value differs from that default.

The following example uses default values. It evaluates a Python expression and records the result or any exception raised:

    from inline_snapshot import snapshot_arg

    def eval_expr(expr, result=None, exception=None):
        result = snapshot_arg(result)
        exception = snapshot_arg(exception)
        try:
            result_value = eval(expr)
            assert result_value == result
            assert exception == None  # (1)!
        except Exception as e:
            assert str(e) == exception
            assert result == None  # (2)!

    def test_numbers():
        eval_expr("1+1")
        eval_expr("1/0")

1. Asserting this snapshot ensures it gets reset when `expr` stops raising an exception.

2. Asserting this snapshot ensures it gets reset when `expr` stops returning a value.

We are using `None` as the default because we want to omit the argument when its value matches the default. Running with `--inline-snapshot=fix` fills in only the arguments whose value differs from the default:

    from inline_snapshot import snapshot_arg

    def eval_expr(expr, result=None, exception=None):
        result = snapshot_arg(result)
        exception = snapshot_arg(exception)
        try:
            result_value = eval(expr)
            assert result_value == result
            assert exception == None
        except Exception as e:
            assert str(e) == exception
            assert result == None

    def test_numbers():
        eval_expr("1+1", result=2)
        eval_expr("1/0", exception="division by zero")

Limitation

The default value must be a literal that can be evaluated with [ast.literal_eval][].

## Backward compatibility

You can pass a `snapshot()` directly as the argument, just as you can nest snapshots. This means that any place where you previously passed `snapshot()` explicitly continues to work. The functions in `inline_snapshot.extra` now use `snapshot_arg()` internally, so they accept both styles.

    from inline_snapshot import snapshot
    from inline_snapshot.extra import prints

    def test_prints():
        with prints():  # (1)!
            print("hello")

        with prints(stdout=snapshot()):  # (2)!
            print("world")

1. New style — no `snapshot()` needed.
2. Old style — `snapshot()` is now redundant and can be removed.

    from inline_snapshot import snapshot
    from inline_snapshot.extra import prints

    def test_prints():
        with prints(stdout="hello\n"):
            print("hello")

        with prints(stdout=snapshot("world\n")):
            print("world")

## Where can I use it?

`snapshot_arg()` can be used in the following contexts:

functions methods constructors context managers

    from inline_snapshot import snapshot_arg

    def check(input, output=...):
        assert input.lower() == snapshot_arg(output)

    def test_check():
        check("Hello", output="hello")

    from inline_snapshot import snapshot_arg

    class Checker:
        def check(self, input, output=...):
            assert input.lower() == snapshot_arg(output)

    def test_check():
        Checker().check("Hello", output="hello")

    from inline_snapshot import snapshot_arg

    class Checker:
        def __init__(self, output=...):
            self.output = snapshot_arg(output)

        def check(self, input, output=...):
            assert input.lower() == self.output

    def test_check():
        Checker(output="hello").check("Hello")

    from contextlib import contextmanager
    from inline_snapshot import snapshot_arg

    @contextmanager
    def make_checker(output=...):
        output = snapshot_arg(output)

        def check(input):
            assert input.lower() == output

        yield check

    def test_check():
        with make_checker("hello") as check:
            check("Hello")

Limitation

Support for `snapshot_arg()` on CPython < 3.11 is limited. You have to pass `snapshot()` as an argument if you want to support older Python versions. `snapshot_arg()` acts as a no-op in that case and passes the snapshot value through.

It is also not possible to use decorators other than `contextmanager` that wrap the function inside another function.

## Difference from `snapshot()`

`snapshot_arg()` is general enough that `snapshot()` itself can be re-implemented with it:

    def snapshot(arg=...):
        return snapshot_arg(arg)

The only difference is that `snapshot_arg()` is slightly slower, because it needs to inspect the calling and called function of your code. This is not a problem when you use `--inline-snapshot=disable` (the default in CI), because `snapshot_arg(x)` is a no-op in this case and simply returns `x`.

## pytest options

It interacts with the following `--inline-snapshot` flags:

- `create` create an argument that is currently missing and has a default of `...`.
- `fix` change the snapshot value, or delete it when a change is necessary to make the test pass.
- `trim` delete the argument when its value matches the default.
- `update` update parts of the value if their representation has changed.