| Crates.io | protest-insta |
| lib.rs | protest-insta |
| version | 1.1.0 |
| created_at | 2025-11-01 19:13:02.876257+00 |
| updated_at | 2025-11-02 17:28:49.4357+00 |
| description | Snapshot testing integration for Protest property-based testing |
| homepage | |
| repository | https://github.com/shrynx/protest |
| max_upload_size | |
| id | 1912322 |
| size | 40,459 |
Property-based snapshot testing integration for Protest and Insta.
protest-insta combines the power of property-based testing with snapshot testing, allowing you to:
Add to your Cargo.toml:
[dev-dependencies]
protest = "0.1"
protest-insta = "0.1"
insta = "1.41"
serde = { version = "1.0", features = ["derive"] }
use protest::{Generator, primitives::IntGenerator, config::GeneratorConfig};
use protest_insta::PropertySnapshots;
use serde::Serialize;
use rand::SeedableRng;
use rand::rngs::StdRng;
#[derive(Serialize)]
struct Point { x: i32, y: i32 }
#[test]
fn test_point_serialization() {
let mut rng = StdRng::seed_from_u64(42);
let config = GeneratorConfig::default();
let generator = IntGenerator::new(0, 100);
let mut snapshots = PropertySnapshots::new("point_serialization");
for _ in 0..5 {
let x = generator.generate(&mut rng, &config);
let y = generator.generate(&mut rng, &config);
let point = Point { x, y };
snapshots.assert_json_snapshot(&point);
}
}
The PropertySnapshots struct manages multiple snapshots with automatic sequential naming:
use protest_insta::PropertySnapshots;
let mut snapshots = PropertySnapshots::new("my_test");
// Creates snapshots named: my_test_0, my_test_1, my_test_2, ...
snapshots.assert_json_snapshot(&data1);
snapshots.assert_json_snapshot(&data2);
snapshots.assert_debug_snapshot(&data3);
Perfect for testing serialization of structured data:
use serde::Serialize;
#[derive(Serialize)]
struct Config {
port: u16,
host: String,
debug: bool,
}
let config = Config {
port: 8080,
host: "localhost".to_string(),
debug: true
};
snapshots.assert_json_snapshot(&config);
Great for testing computation results and non-serializable types:
let results: Vec<i32> = vec![1, 2, 3, 4, 5];
snapshots.assert_debug_snapshot(&results);
For YAML-formatted snapshots:
use serde::Serialize;
#[derive(Serialize)]
struct Settings {
timeout: u64,
retries: u8,
}
let settings = Settings { timeout: 30, retries: 3 };
snapshots.assert_yaml_snapshot(&settings);
The property_snapshot_test function provides a concise API:
use protest::primitives::IntGenerator;
use protest_insta::property_snapshot_test;
use serde::Serialize;
#[derive(Serialize)]
struct Square { value: i32, squared: i32 }
#[test]
fn test_squaring() {
property_snapshot_test(
"square_function",
IntGenerator::new(1, 10),
5, // sample count
42, // seed
|value, snapshots| {
let squared = value * value;
let result = Square { value, squared };
snapshots.assert_json_snapshot(&result);
}
);
}
Test that your types serialize consistently across different inputs:
use protest::primitives::VecGenerator;
use serde::Serialize;
#[derive(Serialize)]
struct Report {
data: Vec<i32>,
summary: String,
}
#[test]
fn test_report_serialization() {
let generator = VecGenerator::new(IntGenerator::new(0, 100), 1, 10);
let mut snapshots = PropertySnapshots::new("reports");
// Test with various vector sizes and contents
for _ in 0..5 {
let data = generator.generate(&mut rng, &config);
let report = Report {
data: data.clone(),
summary: format!("Count: {}", data.len()),
};
snapshots.assert_json_snapshot(&report);
}
}
Verify API responses remain stable:
#[derive(Serialize)]
struct ApiResponse {
status: u16,
body: String,
headers: HashMap<String, String>,
}
#[test]
fn test_api_responses() {
let mut snapshots = PropertySnapshots::new("api_responses");
for status in [200, 404, 500] {
let response = create_response(status);
snapshots.assert_json_snapshot(&response);
}
}
Document computation behavior across inputs:
#[test]
fn test_factorial_outputs() {
property_snapshot_test(
"factorial",
IntGenerator::new(1, 10),
10,
123,
|n, snapshots| {
let result = factorial(n);
snapshots.assert_debug_snapshot(&result);
}
);
}
Detect unexpected changes in output format:
#[test]
fn test_markdown_generation() {
let mut snapshots = PropertySnapshots::new("markdown");
for _ in 0..5 {
let document = generate_document(&mut rng);
let markdown = document.to_markdown();
snapshots.assert_debug_snapshot(&markdown);
}
}
Always use a seeded RNG for reproducible snapshots:
use rand::SeedableRng;
use rand::rngs::StdRng;
let mut rng = StdRng::seed_from_u64(42); // ✅ Reproducible
// let mut rng = rand::thread_rng(); // ❌ Non-deterministic
Keep snapshot counts reasonable (5-10) to make reviews manageable:
let mut snapshots = PropertySnapshots::new("test");
for _ in 0..5 { // ✅ Reasonable
// ...
}
// for _ in 0..1000 { // ❌ Too many snapshots
Choose clear, descriptive names for snapshot groups:
PropertySnapshots::new("user_profile_json") // ✅ Clear
PropertySnapshots::new("test1") // ❌ Unclear
Use the same base name for related test scenarios:
let mut snapshots = PropertySnapshots::new("sorting_algorithms");
snapshots.assert_debug_snapshot(&bubble_sort_result);
snapshots.assert_debug_snapshot(&quick_sort_result);
snapshots.assert_debug_snapshot(&merge_sort_result);
Use Insta's review workflow:
# Review all pending snapshots
cargo insta review
# Accept all snapshots
cargo insta accept
# Reject all snapshots
cargo insta reject
#[test]
fn test_serialization() {
let data = MyStruct { value: 42 };
insta::assert_json_snapshot!(data);
}
Limitations:
#[test]
fn test_serialization_property_based() {
property_snapshot_test(
"serialization",
IntGenerator::new(0, 1000),
10,
42,
|value, snapshots| {
let data = MyStruct { value };
snapshots.assert_json_snapshot(&data);
}
);
}
Benefits:
See the examples/ directory for complete working examples:
json_snapshots.rs - JSON snapshot testing with complex structuresdebug_snapshots.rs - Debug snapshots for computation resultsproperty_snapshot_test.rs - Using the helper functionRun examples with:
cargo run --example json_snapshots
cargo run --example debug_snapshots
cargo run --example property_snapshot_test
PropertySnapshotsManages a group of related snapshots with automatic naming.
new(base_name) - Create a new snapshot helperassert_json_snapshot(&value) - Create a JSON snapshotassert_debug_snapshot(&value) - Create a debug snapshotassert_yaml_snapshot(&value) - Create a YAML snapshotreset() - Reset the counter to 0count() - Get the current counter valueproperty_snapshot_testHelper function for concise property-based snapshot testing.
test_name: &str - Base name for snapshotsgenerator: G - Generator for test inputssample_count: usize - Number of samples to generateseed: u64 - RNG seed for reproducibilitytest_fn: F - Test function receiving each generated valueThis crate is built on top of Insta, so all Insta features work seamlessly:
cargo insta reviewinsta::SettingsA: Use Insta's CLI tool:
cargo install cargo-insta
cargo insta review
A: By default, in a snapshots/ directory next to your test file. Insta manages this automatically.
A: Yes! Snapshots are part of your test suite and should be version controlled.
A: Start with 5-10. More samples give better coverage but make reviews more tedious.
A: Absolutely! protest-insta is just a helper layer on top of Insta. Mix and match freely.
Contributions are welcome! Please see the main Protest repository for contribution guidelines.
Licensed under the MIT license. See LICENSE for details.