Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@ All notable changes to this project will be documented in this file.

*NOTE:* Version 0.X.X might have breaking changes in bumps of the minor version number. This is because the project is still in early development and the API is not yet stable. It will still be marked clearly in the release notes.

## [0.8.0]
Icons:
- 🆕 New feature
- 🐞 Bug fix
- 🗑️ Deprecated feature
- 💥 Breaking change
- 🔄 Changed

## [0.8.0] - 2025-10-18
- 🆕 Used integration with toolit to make snappylapy commands available for AI coding assistants
- ❗Deprecated support for Python 3.9. Snappylapy now requires Python 3.10 or higher. Add python 3.14 to the test matrix.
- 🆕 Add a `@configure_snappylapy()` decorator as an alternative for using @pytest.mark.snappylapy for improved documentation support, type checking and IDE integration.
- 🔄 Improve documentation and examples for using snappylapy for advanced snapshot testing scenarios.
- 🗑️ Deprecated support for Python 3.9. Snappylapy now requires Python 3.10 or higher. Add Python 3.14 to the test matrix.

## [0.7.0] - 2025-08-27
- 🆕 Added csv serializer for pandas dataframes for making the deserialization work on windows and make a more suitable and human readable format.
Expand Down Expand Up @@ -43,9 +52,10 @@ All notable changes to this project will be documented in this file.
- Add snappylapy parametization functionality to snappylapy and allow it to be loaded in the load_snapshot fixture
- Do reporting showing count of updated, created and deleted snapshots
- **Breaking Changes**
- Change setting of path to only have a single path configurable and update such it is configured through the marker
- Make the folder names of __snapshots__ and __test_results__ enforced to be fixed (needed for cleanup and diffing)
- Rename output file names of snapshots to match [filename][testname][name].extention conversion
- 💥 Change setting of path to only have a single path configurable and update such it is configured through the marker
- 💥 Make the folder names of __snapshots__ and __test_results__ enforced to be fixed (needed for cleanup and diffing)
- 💥 Rename output file names of snapshots to match [filename][testname][name].extention conversion

## [0.2.1] - 2025-01-13
- Added missing dependency for typer to make the CLI work

Expand Down
114 changes: 108 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Snappylapy

<div align="center">
<img src="snappylapy-logo.svg" alt="Snappylapy Logo" style="width:200px;"/>

<h1> Snappylapy</h1>
<h3>Pytest Plugin for Snapshot Testing</h3>
</div>
<p align="center">
<strong>
Effortlessly capture, diff, and reuse complex, non-deterministic, and AI-generated data structures for reliable and fast pytest runs — ideal for data engineering and machine learning workflows.
</strong>
</p>
<!-- Badges -->
<p align="center">
<a href="https://github.com/martinmoldrup/snappylapy/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/status-beta-yellow" alt="Beta Status"/></a>
<a href="https://github.com/martinmoldrup/snappylapy/blob/main/LICENSE"><img src="https://img.shields.io/github/license/martinmoldrup/snappylapy" alt="License"/></a>
<a href="https://martinmoldrup.github.io/snappylapy/"><img src="https://img.shields.io/badge/docs-MkDocs-blue" alt="Documentation"/></a>
<a href="https://github.com/martinmoldrup/snappylapy/blob/main/CHANGELOG.md"><img src="https://img.shields.io/github/v/tag/martinmoldrup/snappylapy?label=version&logo=github&color=purple" alt="Version"/></a>
<br/>
<!-- Quality Assurance steps. Note the success of these steps is enforced in the CI pipeline. -->
<a href="https://github.com/martinmoldrup/snappylapy/actions/workflows/continuous_integration.yaml"><img src="https://img.shields.io/badge/type%20check-mypy-success?logo=python" alt="mypy"/></a>
<a href="https://github.com/martinmoldrup/snappylapy/actions/workflows/continuous_integration.yaml"><img src="https://img.shields.io/badge/lint-ruff-success?logo=ruff" alt="ruff"/></a>
<a href="https://github.com/martinmoldrup/snappylapy/actions/workflows/continuous_integration.yaml"><img src="https://img.shields.io/badge/tested%20with-pytest-success?logo=pytest" alt="pytest"/></a>
<a href="https://github.com/martinmoldrup/snappylapy/blob/main/pyproject.toml"><img src="https://img.shields.io/badge/package%20manager-uv-success?logo=uv" alt="uv"/></a>
<a href="https://github.com/martinmoldrup/toolit"><img src="https://img.shields.io/badge/command%20runner-toolit-success" alt="toolit"/></a>
</p>

---

Welcome to **Snappylapy**, a powerful and intuitive snapshot testing plugin for Python's pytest framework. Snappylapy simplifies the process of capturing and verifying snapshots of your data, ensuring your code behaves as expected across different runs. With Snappylapy, you can save snapshots in a human-readable format and deserialize them for robust integration testing, providing a clear separation layer to help isolate errors and maintain code integrity.

Snappylapy is following the api-style of the very popular Jest testing framework, making it familiar and easy to use for JavaScript developers.
Expand Down Expand Up @@ -40,7 +66,7 @@ Snapshot testing is a powerful technique for verifying the output of your code b

When working on a test suite for a project, it’s important to ensure tests are independent. This is to avoid situations where changes in one part of the code cause failures in tests for other unrelated areas, making it challenging to isolate and fix errors. Snappylapy addresses this by providing a mechanism to capture snapshots of your data and use them in your later tests, ensuring that each component can be tested independently. While also making sure that they are dependent enough to test the integration between them. It provides serialization and deserialization of the snapshots, making it easy to reuse them in different test cases. This is aimed at function working with large and complex data structures (dataframes or large nested dictionaries.)

### Example
### Example - Basic Snapshot Test

`test_expect_snapshot_dict.py`
```python
Expand All @@ -59,6 +85,7 @@ def test_snapshot_dict(expect: Expect):

In this example, `snappylapy` captures the output of `my_function` and compares it against a stored snapshot. If the output changes unexpectedly, pytest will flag the test, allowing you to review the differences and ensure your code behaves as expected.

### Example - Loading Snapshots for Integration Testing
Snappylapy can use the snapshots created for inputs in another test. You can think of it as automated/easier mock data generation and management.

`test_expect_and_loadsnapshot.py`
Expand All @@ -82,14 +109,89 @@ def test_load_snapshot_from_file(load_snapshot: LoadSnapshot):
assert data == {"name": "John Doe", "age": 31}
```

This can be great for external dependencies, for example an AI service, that might change response over time. With this approach we can isolate the changes to the service and still make succeeding tests pass.
This can be great for external dependencies, for example an AI service, that might change response over time.

Snapshot testing with Snappylapy helps keep unit tests independent. By capturing and loading snapshots of test data, each test checks only its own behavior and does not rely on the results of other tests. This means that if one test fails, it won’t cause unrelated tests to fail, making it easier to find and fix problems quickly.

### Example - Custom Snapshot Location
In snappylapy, you can customize the output directory for your snapshots. This is useful for organizing your test data or separating snapshots for different test suites.

`test_expect_snapshot_custom_dir.py`
```python
@pytest.mark.snappylapy(output_dir="custom_dir")
def test_snapshot_with_custom_directories(expect: Expect):
"""Test snapshot with custom directories."""
expect.string("Hello World").to_match_snapshot()
```
In this example, the snapshot for the test will be stored in the `custom_dir` directory instead of the default root directory location. This allows you to organize your snapshots based on your project's structure or testing needs.

```
custom_dir
__snapshots__
[test_expect_snapshot_custom_dir][test_snapshot_with_custom_directories].string.txt
__test_results__
[test_expect_snapshot_custom_dir][test_snapshot_with_custom_directories].string.txt
```

### Example - Multiple Snapshot Folders
A common use case is to have multiple test case folders, each with their own input data. Snappylapy can handle this with ease using the `foreach_folder_in` parameter.

`test_data` example folder structure
```
test_cases
case1
input.json
case2
input.json
...
```
When we add the `foreach_folder_in` parameter to the `snappylapy` marker, it will run the test for each folder inside the specified directory, and it will include a `test_directory` fixture to access the current folder being tested.

> Important: You must ensure that the test function has a `test_directory` input parameter of type `pathlib.Path` to access the current test case folder. You cannot use another name for this parameter.

`test_expect_snapshot_multiple_folders.py`
```python
import json

def transform_data(data: dict) -> dict:
"""A sample transformation function."""
# Example transformation: add a new key-value pair
data["transformed"] = True
return data

@pytest.mark.snappylapy(foreach_folder_in="test_data")
def test_snapshot_parametrized_multiple_test_case_folders(test_directory: pathlib.Path, expect: Expect):
"""Test snapshot with multiple folders."""
data = json.loads((test_directory / "input.json").read_text())
transformed_data = transform_data(data)
expect.dict(transformed_data).to_match_snapshot()
```

After running the test, snapshots will be created for each folder inside `test_data`, allowing you to manage and compare snapshots for multiple test cases easily.

```
test_cases
case1
input.json
__snapshots__
[test_expect_snapshot_multiple_folders][test_snapshot_parametrized_multiple_test_case_folders].dict.json
case2
input.json
__snapshots__
[test_expect_snapshot_multiple_folders][test_snapshot_parametrized_multiple_test_case_folders].dict.json
...
```

This setup provides a clear and organized way to manage snapshots for multiple test cases, making it easier to maintain and review your test data.

## The output structure

The results is split into two folders, for ease of comparison, and for handling stochastic/variable outputs (timestamps, generated ids, llm outputs, third party api responses etc).

- __test_results__: Updated every time the tests is ran. Compare with snapshots when doing snapshot style assertions. Add this to your .gitignore file.
- __snapshots__: Updated only when --snapshot-update flag is used when running the test suite. Commit this to your version control system.
- `__test_results__`: Updated every time the tests is ran. Compare with snapshots when doing snapshot style assertions. Add this to your .gitignore file (this can be done automatically when using the `snappylapy init` command).
- `__snapshots__`: Updated only when `--snapshot-update` flag is used when running the test suite, or using the `snappylapy update` command. Commit this to your version control system.

When you perform partial matches against snapshots, any changes in your test outputs are saved to the `__test_results__` folder. This means you can review differences between your current test results and the stored snapshots in `__snapshots__` without modifying your baseline snapshots. As a result, you can easily track changes and debug issues, while keeping your reference snapshots unchanged until you intentionally update them. It is easy to compare the two files using a diff tool or your code editor's built-in comparison features.

## Usage
Snapshots can be updated when running pytest:
Expand Down
3 changes: 3 additions & 0 deletions __snapshots__/[test_file][test_function].dataframe.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
,key1,key2
0,value1,test1
1,value2,test2

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"py/object": "test_snappylapy_new.test_custom_type.<locals>.CustomObject"
}
Loading
Loading