Module: Synthra::PropertyTesting

Defined in:
lib/synthra/property_testing.rb

Instance Method Summary collapse

Instance Method Details

#shrink_counterexample(schema_name, failing_record, registry: nil) {|Hash| ... } ⇒ Hash

Shrink a failing test case to find minimal counterexample

When a property fails, attempts to shrink the failing record to find a smaller counterexample.

Parameters:

  • schema_name (String)

    schema name

  • failing_record (Hash)

    record that caused failure

  • registry (Registry, nil) (defaults to: nil)

    registry

Yields:

  • (Hash)

    block to verify property

Returns:

  • (Hash)

    minimal counterexample



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/synthra/property_testing.rb', line 115

def shrink_counterexample(schema_name, failing_record, registry: nil, &block)
  registry ||= Registry.new
  schema = registry.schema(schema_name)

  current = failing_record.dup
  improved = true

  while improved
    improved = false

    # Try removing optional fields
    current.keys.each do |key|
      next unless schema.field(key)&.optional?

      candidate = current.dup
      candidate.delete(key)

      begin
        block.call(candidate)
      rescue RSpec::Expectations::ExpectationNotMetError
        # Still fails with smaller record
        current = candidate
        improved = true
        break
      end
    end

    # Try simplifying array values
    current.each do |key, value|
      next unless value.is_a?(Array) && value.length > 1

      candidate = current.dup
      candidate[key] = value[0..0] # Single element

      begin
        block.call(candidate)
      rescue RSpec::Expectations::ExpectationNotMetError
        current = candidate
        improved = true
        break
      end
    end
  end

  current
end

#verify_property(schema_name, count: 100, seed: nil, mode: :random, registry: nil) {|Hash| ... } ⇒ Object

Verify a property holds for generated data

Generates multiple records from a schema and verifies a property holds for all of them. Raises if property fails for any record.

Examples:

verify_property("User") do |user|
  expect(user["email"]).to match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
end

Parameters:

  • schema_name (String)

    schema name to generate from

  • count (Integer) (defaults to: 100)

    number of records to generate (default: 100)

  • seed (Integer, nil) (defaults to: nil)

    random seed for reproducibility

  • mode (Symbol) (defaults to: :random)

    generation mode (default: :random)

  • registry (Registry, nil) (defaults to: nil)

    registry for schema lookup

Yields:

  • (Hash)

    block to verify property

Yield Parameters:

  • record (Hash)

    generated record

Raises:

  • (RSpec::Expectations::ExpectationNotMetError)

    if property fails



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/synthra/property_testing.rb', line 51

def verify_property(schema_name, count: 100, seed: nil, mode: :random, registry: nil)
  registry ||= Registry.new
  schema = registry.schema(schema_name)

  records = schema.generate_many(count, seed: seed, mode: mode, registry: registry)

  records.each_with_index do |record, idx|
    begin
      yield(record)
    rescue RSpec::Expectations::ExpectationNotMetError => e
      raise RSpec::Expectations::ExpectationNotMetError,
            "Property failed for record #{idx + 1}/#{count} (seed: #{seed || 'random'}):\n" \
            "#{e.message}\n" \
            "Record: #{record.inspect}"
    end
  end
end

#verify_property_stream(schema_name, count: 1000, seed: nil, mode: :random, registry: nil) {|Hash| ... } ⇒ Object

Verify a property holds for a stream of generated data

Generates records lazily and verifies property for each. More memory-efficient for large counts.

Examples:

verify_property_stream("Order", count: 10000) do |order|
  expect(order["items"]).to be_an(Array)
end

Parameters:

  • schema_name (String)

    schema name to generate from

  • count (Integer) (defaults to: 1000)

    number of records to generate

  • seed (Integer, nil) (defaults to: nil)

    random seed

  • mode (Symbol) (defaults to: :random)

    generation mode

  • registry (Registry, nil) (defaults to: nil)

    registry for schema lookup

Yields:

  • (Hash)

    block to verify property



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/synthra/property_testing.rb', line 86

def verify_property_stream(schema_name, count: 1000, seed: nil, mode: :random, registry: nil)
  registry ||= Registry.new
  schema = registry.schema(schema_name)

  stream = schema.generate_many_stream(count, seed: seed, mode: mode, registry: registry)

  stream.each_with_index do |record, idx|
    begin
      yield(record)
    rescue RSpec::Expectations::ExpectationNotMetError => e
      raise RSpec::Expectations::ExpectationNotMetError,
            "Property failed for record #{idx + 1}/#{count} (seed: #{seed || 'random'}):\n" \
            "#{e.message}\n" \
            "Record: #{record.inspect}"
    end
  end
end