inline-snapshot

repository·main·Indexed 20 days ago

https://github.com/15r10nk/inline-snapshot

A Python golden master/snapshot/approval testing library that integrates with pytest to record and update expected values directly in the source code. It supports basic inline assertions via snapshot(), complex data types, inequality operators, and automatic code formatting with black. The library categorizes snapshot changes into Create, Fix, Trim, and Update to streamline the review process.

Tokens
25.5K
Snippets
90
Records
124
Agent score
70%

What's inside inline-snapshot

  1. Available third-party extensions for inline-snapshot

    main

    There are several third-party extensions available to integrate inline-snapshot with other frameworks and data libraries:

    • inline-snapshot-django: Extensions for using inline-snapshot to test Django projects.
    • inline-snapshot-pandas: pandas integration for inline-snapshot (insider only).
  2. Check if a value is in a snapshot

    main

    You can use the in operator to verify if specific values exist within a snapshot. When you perform these checks, the generated snapshot will store a list of all values that were tested using the in operator.

    from inline_snapshot import snapshot
    
    def test_something():
        s = snapshot()
    
        assert 5 in s
        assert 8 in s
    
        for v in ["a", "b"]:
            assert v in s
  3. Supported snapshot operations

    main

    You can use snapshot(x) in assertions with a specific set of operations.

    Important Constraint: One snapshot instance can only be used with exactly one operation. Attempting to reuse a snapshot for multiple different operations will result in an error.

    Supported operations:

    • value == snapshot(): Equality comparison.
    • value <= snapshot(): Comparison to ensure a value stays within a bound (e.g., checking that an algorithm's iterations decrease over time).
    • value in snapshot(): Membership check against a known set of values.
    • snapshot()[key]: Accessing a key to generate new sub-snapshots on demand.
  4. Use unmanaged snapshot values to retain developer control

    main

    By default, inline-snapshot manages everything inside snapshot(...). However, you can use "unmanaged" types that the library will ignore and refuse to update or fix, even if they cause tests to fail. This is useful for parts of the snapshot you want to control manually.

    Unmanaged types include:

    • dirty-equals expressions
    • Dynamic code inside Is(...)
    • Snapshots nested inside other snapshots
    • f-strings

    You can also define your own unmanaged types using the @declare_unmanaged decorator. Unmanaged types must be placed within supported containers like list, tuple, dict, namedtuple, dataclass, or attrs to be handled correctly.

    from inline_snapshot import declare_unmanaged, snapshot
    
    @declare_unmanaged
    class AllEqual:
        def __init__(self, value):
            self.value = value
    
        def __eq__(self, other):
            return all(o == self.value for o in other)
    
    def test_all_equal():
        # AllEqual is unmanaged, but the snapshot(1) inside it is managed
        assert {"text": "hello", "values": [1, 1, 1]} == snapshot(
            {"text": "hello", "values": AllEqual(snapshot(1))}
        )
  5. Understand default pytest flags for inline-snapshot

    main

    If you run pytest without any --inline-snapshot options, the plugin uses default flags based on your environment:

    • Interactive Terminal: Uses --inline-snapshot=create,review.
    • CPython 3.10 or older: Uses --inline-snapshot=short-report.
    • CI Environments: The default behavior is equivalent to --inline-snapshot=disable.
  6. Advanced usage patterns for snapshot()

    main

    The snapshot() function supports various comparison and structural patterns:

    • Numeric Limits: Use comparison operators like <= and >= against a snapshot.
    • Set Membership: Use the in operator to check if a value exists within a snapshot.
    • Sub-snapshots (Indexing/Key Access): Create complex snapshots that can be accessed via keys or indices at runtime.
    • External Storage: Use external("uuid:...") to store snapshots in external files instead of inlining them.
    • Large/Multiline Strings: Use outsource() for large strings or simply use snapshot() for multiline string comparisons.
    from inline_snapshot import external, outsource, snapshot
    
    def test_advanced_patterns():
        # testing for numeric limits
        assert 5 <= snapshot()
        
        # test if something is part of a set
        assert "h" in snapshot("hello")
    
        # create sub-snapshots at runtime
        s = snapshot()
        assert s[0]["key"] == "value"
    
        # external storage
        assert outsource("large string") == snapshot()
    
        # multiline strings
        assert "line1\nline2" == snapshot()
  7. Insider features: Create and fix assertions without snapshot()

    main
    The insider version allows you to create and fix normal assertions without explicitly using the snapshot() function. This is particularly useful for integrating inline-snapshot magic into large existing codebases or when you want to use the functionality without adding inline-snapshot as a formal test dependency to your project.
  8. Handle dirty-equals expressions in snapshots

    main

    You can instruct inline-snapshot to use specific dirty-equals expressions (from the dirty-equals library) in your snapshots.

    Note: This is a one-way operation. Because dirty-equals expressions are considered unmanaged, inline-snapshot will not automatically change them if you update your @customize implementation later. It assumes any existing expression was placed there by the user.

    For simple cases, you can return the expression type directly (e.g., IsNow) instead of using the builder.

    from inline_snapshot.plugin import customize
    from dirty_equals import IsNow
    
    @customize
    def is_now_handler(value):
        if value == IsNow():
            return IsNow
  9. Test inline-snapshot workflows with the Example class

    main

    The inline_snapshot.testing.Example class allows you to simulate and test inline-snapshot workflows (like creating or fixing snapshots) within your own test suites. This is particularly useful if you are building libraries that depend on inline-snapshot.

    Key Concepts

    • Immutability: Example objects are immutable. Calling a run_* method does not modify the existing object; instead, it returns a new Example object containing the updated files.
    • Isolation: An Example is not connected to any directory. A temporary directory is only created when a run_* method is invoked.
    • Chaining: Because run_* methods return new Example instances, you can chain them to perform complex, multi-step tests where each step operates on the files modified by the previous step.
    from inline_snapshot import snapshot
    from inline_snapshot.testing import Example
    
    def test_workflow():
        # Create initial example
        e = Example({"test.py": "assert 1 == snapshot()"})
        
        # Chain runs: first create, then fix
        (e.run_pytest(["--inline-snapshot=create"], ...)
           .run_pytest(["--inline-snapshot=fix"], ...))
  10. Use dirty-equals with inline-snapshot

    main

    inline-snapshot supports dirty-equals expressions. This allows you to use matchers (like IsStr from the dirty_equals library) within your snapshot. When the snapshot is updated, inline-snapshot will preserve these matchers instead of replacing them with literal values, allowing you to keep parts of your snapshot dynamic.

    from dirty_equals import IsStr
    from inline_snapshot import snapshot
    
    def user_response():
        return {"id": "usr_123", "name": "Mia"}
    
    def test_user_response():
        assert user_response() == snapshot(
            {"id": IsStr(regex=r"usr_\d+"), "name": "Mia"}
        )
  11. How inner snapshots work

    main

    You can nest snapshot() calls inside other snapshots to handle complex logic or reuse data.

    Conditional Snapshots

    Use Python logic (like if/else) inside a snapshot to handle version-specific or environment-specific data. For example, you can switch between different snapshot values based on a library version.

    Common Snapshot Parts

    You can extract a common part of a snapshot into its own variable by calling snapshot() on it. This allows you to reuse that specific piece of data across multiple assertions while still allowing inline-snapshot to manage and update it if necessary.

    from inline_snapshot import snapshot
    
    def some_data(name):
        return {"header": "really long header\n" * 5, "your name": name}
    
    def test_function():
        # Extract common part
        header = snapshot("really long header\nreally long header\nreally long header\nreally long header\nreally long header")
    
        assert some_data("Tom") == snapshot({"header": header, "your name": "Tom"})
        assert some_data("Bob") == snapshot({"header": header, "your name": "Bob"})