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])
... 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")
-
Asserting this snapshot ensures it gets reset when
exprstops raising an exception. -
Asserting this snapshot ensures it gets reset when
exprstops returning a value.
We are using None as the default because we want to omit the argument when its value matches the default. Using ... would force inline-snapshot to always create it.
Running with --inline-snapshot=create 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")
- New style — no
snapshot()needed. - 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:
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:
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:
createcreate an argument that is currently missing and has a default of....fixchange the snapshot value, or delete it when a change is necessary to make the test pass.trimdelete the argument when its value matches the default.updateupdate parts of the value if their representation has changed.