Module: Synthra::SnapshotTesting

Defined in:
lib/synthra/snapshot_testing.rb

Overview

Snapshot Testing Support

Provides helpers for snapshot testing generated data. Ensures schema changes are detected and intentional.

Examples:

RSpec integration

RSpec.describe "Schema Snapshots" do
  include Synthra::SnapshotTesting

  Synthra.each_schema do |schema|
    it "#{schema.name} generates stable output" do
      expect_snapshot(schema, seed: 42)
    end
  end
end

Defined Under Namespace

Classes: SnapshotMatcher

Constant Summary collapse

SNAPSHOT_DIR =

Default snapshot directory

"spec/snapshots/synthra"

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.snapshot_dirObject

Snapshot directory path



26
27
28
# File 'lib/synthra/snapshot_testing.rb', line 26

def snapshot_dir
  @snapshot_dir
end

Class Method Details

.deep_sort(obj) ⇒ Object

Deep sort helper for module methods



112
113
114
115
116
117
118
119
120
121
# File 'lib/synthra/snapshot_testing.rb', line 112

def self.deep_sort(obj)
  case obj
  when Hash
    obj.sort.to_h.transform_values { |v| deep_sort(v) }
  when Array
    obj.map { |v| deep_sort(v) }
  else
    obj
  end
end

.generate_all_snapshots(registry, seed: 42) ⇒ Object

Generate snapshots for all schemas

Parameters:

  • registry (Registry)

    schema registry

  • seed (Integer) (defaults to: 42)

    seed for deterministic generation



72
73
74
75
76
77
78
79
80
81
# File 'lib/synthra/snapshot_testing.rb', line 72

def self.generate_all_snapshots(registry, seed: 42)
  FileUtils.mkdir_p(snapshot_dir)
  
  registry.schemas.each do |name, schema|
    path = File.join(snapshot_dir, "#{name.downcase}.json")
    generated = schema.generate(seed: seed)
    File.write(path, JSON.pretty_generate(deep_sort(generated)))
    puts "Generated snapshot: #{path}"
  end
end

.verify_all_snapshots(registry, seed: 42) ⇒ Array<String>

Verify all snapshots match

Parameters:

  • registry (Registry)

    schema registry

  • seed (Integer) (defaults to: 42)

    seed for deterministic generation

Returns:

  • (Array<String>)

    list of mismatched schema names



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/synthra/snapshot_testing.rb', line 89

def self.verify_all_snapshots(registry, seed: 42)
  mismatches = []
  
  registry.schemas.each do |name, schema|
    path = File.join(snapshot_dir, "#{name.downcase}.json")
    
    unless File.exist?(path)
      mismatches << "#{name}: snapshot not found"
      next
    end
    
    generated = schema.generate(seed: seed)
    expected = JSON.parse(File.read(path))
    
    unless deep_sort(generated) == deep_sort(expected)
      mismatches << name
    end
  end
  
  mismatches
end

Instance Method Details

#expect_snapshot(schema, seed: 42, update: false) ⇒ Boolean

Generate and compare snapshot for a schema

Parameters:

  • schema (Schema)

    the schema to snapshot

  • seed (Integer) (defaults to: 42)

    seed for deterministic generation

  • update (Boolean) (defaults to: false)

    if true, update the snapshot file

Returns:

  • (Boolean)

    true if snapshot matches



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/synthra/snapshot_testing.rb', line 38

def expect_snapshot(schema, seed: 42, update: false)
  snapshot_path = snapshot_path_for(schema)
  generated = schema.generate(seed: seed)
  generated_json = JSON.pretty_generate(sort_hash(generated))

  if update || !File.exist?(snapshot_path)
    write_snapshot(snapshot_path, generated_json)
    return true
  end

  expected = File.read(snapshot_path)
  
  if generated_json == expected
    true
  else
    report_snapshot_mismatch(schema.name, expected, generated_json)
    false
  end
end

#match_snapshot(schema_name) ⇒ SnapshotMatcher

RSpec matcher for snapshot testing

Parameters:

  • schema_name (String)

    schema name for snapshot file

Returns:



63
64
65
# File 'lib/synthra/snapshot_testing.rb', line 63

def match_snapshot(schema_name)
  SnapshotMatcher.new(schema_name, snapshot_dir: SnapshotTesting.snapshot_dir)
end

#report_snapshot_mismatch(name, expected, actual) ⇒ Object (private)



147
148
149
150
151
152
153
154
155
# File 'lib/synthra/snapshot_testing.rb', line 147

def report_snapshot_mismatch(name, expected, actual)
  puts "\nSnapshot mismatch for #{name}:"
  puts "Expected:"
  puts expected.lines.first(10).join
  puts "..."
  puts "\nActual:"
  puts actual.lines.first(10).join
  puts "..."
end

#snapshot_path_for(schema) ⇒ Object (private)



125
126
127
128
129
# File 'lib/synthra/snapshot_testing.rb', line 125

def snapshot_path_for(schema)
  dir = SnapshotTesting.snapshot_dir
  FileUtils.mkdir_p(dir)
  File.join(dir, "#{schema.name.downcase}.json")
end

#sort_hash(obj) ⇒ Object (private)



136
137
138
139
140
141
142
143
144
145
# File 'lib/synthra/snapshot_testing.rb', line 136

def sort_hash(obj)
  case obj
  when Hash
    obj.sort.to_h.transform_values { |v| sort_hash(v) }
  when Array
    obj.map { |v| sort_hash(v) }
  else
    obj
  end
end

#write_snapshot(path, content) ⇒ Object (private)



131
132
133
134
# File 'lib/synthra/snapshot_testing.rb', line 131

def write_snapshot(path, content)
  FileUtils.mkdir_p(File.dirname(path))
  File.write(path, content)
end