oh_snap 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4c41a3989a772bbd58cf49c19f7d44b4b065050f64a605b0050803442a306848
4
+ data.tar.gz: 4d3cb106e11b1d74d4d9dda4212914f77a3e4fd5c65b1bab1db0500909d34460
5
+ SHA512:
6
+ metadata.gz: 6b4fdf4ce22b5ed0ed7cc9da8d95a03e99bd02676f52179e52c26c5c8083fc220da929f2d14cf0c4490a2a7d570970d7cd7acca7db482601aa69421670859ae7
7
+ data.tar.gz: 03deebc5fb259d736c5058f6cbbd5418210ebea020d18cd8d5d6a6cdc48991a2c505d1c1dd172edf6b136525665da7b034ad100fd2ca4569f95db89b2434770b
data/LICENSE.txt ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025 Miguel Renom
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,217 @@
1
+ # OhSnap
2
+
3
+ **OhSnap** is a Ruby gem for snapshot testing with helpers for Minitest and RSpec to name snapshot files after the tests that create them.
4
+ It is built to be asserted via the equality comparator so you can use any assertion that you like that is based on it.
5
+
6
+ ## Features
7
+
8
+ - **Compare snapshots with `#==`**: No special assertions are needed; just use equality syntax.
9
+ - **Split in multiple files**: Stores snapshots in separate files for easy inspection.
10
+ - **Automatic snapshot naming for Minitest and RSpec**: Automatically names snapshot files based on the test name.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'oh_snap'
18
+ ```
19
+
20
+ and then execute:
21
+
22
+ ```bash
23
+ bundle install
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```bash
29
+ gem install oh_snap
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ Use the `OhSnap::Helper` to set up your snapshot registry on every test run:
35
+
36
+ ```ruby
37
+ require 'oh_snap/helper'
38
+
39
+ class MyTest < Test::Unit::TestCase
40
+ include OhSnap::Helper
41
+
42
+ def startup
43
+ super
44
+ setup_snapshots(path: File.dirname(__FILE__))
45
+ end
46
+
47
+ def test_a_snapshot_matches
48
+ assert_equal snapshot(name: 'snapshot_name'), 'some value'
49
+ end
50
+
51
+ def shutdown
52
+ save_snapshots
53
+ super
54
+ end
55
+ end
56
+ ```
57
+
58
+ You are responsible for specifying the base location for the `__snapshots__` directory in the `path:` argument and naming your snapshots passing `name:` in each call to `#snapshot`.
59
+
60
+ Bear in mind that order is important when comparing snapshots to values:
61
+
62
+ ```ruby
63
+ # Bad
64
+ assert 'something' == snapshot
65
+ assert_equal 'something', snapshot
66
+
67
+ # Good
68
+ assert snapshot == 'something'
69
+ assert_equal snapshot, 'something'
70
+ ```
71
+
72
+ If you really do not care about choosing your assertion, use the `snapshot_matches` helper method included with `OhSnap::Helper`:
73
+
74
+ ```ruby
75
+ assert snapshot_matches('something')
76
+ expect(snapshot_matches('something')).to be true
77
+ ```
78
+
79
+ ### Minitest and RSpec
80
+
81
+ Helpers for RSpec and Minitest make things easier because they name the snapshot file automagically based on the test name:
82
+
83
+ #### Minitest
84
+
85
+ In your test file:
86
+
87
+ ```ruby
88
+ require 'minitest/autorun'
89
+ require 'oh_snap/minitest'
90
+
91
+ class MyTest < Minitest::Test
92
+ include OhSnap::Minitest
93
+
94
+ def test_example
95
+ result = perform_complex_operation
96
+ assert snapshot == result
97
+ end
98
+
99
+ def test_example_with_assert_equal
100
+ result = perform_complex_operation
101
+ assert_equal snapshot, result
102
+ end
103
+
104
+ def test_example_with_matcher
105
+ result = perform_complex_operation
106
+ assert snapshot_matches(result)
107
+ end
108
+
109
+ def test_custom_named_snapshot
110
+ result = perform_another_operation
111
+ assert_equal snapshot(name: 'custom_snapshot_name'), result
112
+ end
113
+
114
+ def test_custom_named_snapshot_with_matcher
115
+ result = perform_another_operation
116
+ assert snapshot_matches(result, name: 'custom_snapshot_name')
117
+ end
118
+ end
119
+ ```
120
+
121
+ #### RSpec
122
+
123
+ In your test file:
124
+
125
+ ```ruby
126
+ require 'oh_snap/rspec'
127
+
128
+ RSpec.configure do |config|
129
+ config.include OhSnap::RSpec
130
+ end
131
+
132
+ RSpec.describe 'My feature' do
133
+ it 'produces the correct output' do
134
+ result = perform_complex_operation
135
+ expect(snapshot).to eq result
136
+ end
137
+
138
+ it 'also produces the correct output' do
139
+ result = perform_complex_operation
140
+ expect(snapshot_matches(result)).to be true
141
+ end
142
+
143
+ it 'produces the correct output with custom snapshot name' do
144
+ result = perform_another_operation
145
+ expect(snapshot(name: 'custom_snapshot_name')).to eq result
146
+ end
147
+
148
+ it 'also produces the correct output with custom snapshot name' do
149
+ result = perform_another_operation
150
+ expect(snapshot_matches(result, name: 'custom_snapshot_name')).to be true
151
+ end
152
+ end
153
+
154
+ ```
155
+
156
+ In both cases, snapshots will be saved in the `__snapshots__` directory adjacent to the test file.
157
+
158
+ ## Updating Snapshots
159
+
160
+ To update existing snapshots, set the `UPDATE_SNAPSHOTS` environment variable:
161
+
162
+ ```bash
163
+ UPDATE_SNAPSHOTS=1 bundle exec rake test
164
+ ```
165
+
166
+ This will regenerate snapshots for tests that fail due to mismatches.
167
+
168
+ Notice that setting any value for `UPDATE_SNAPSHOTS` will be interpreted as setting the write mode as `true`.
169
+ If you do not want to update snapshots, do not set `UPDATE_SNAPSHOTS` at all.
170
+ The latter should be the case in your CI environment.
171
+
172
+ ## Configuration
173
+
174
+ Global configuration can be set via `OhSnap.config`:
175
+ ```ruby
176
+ OhSnap.config.tap do |config|
177
+ config.serializer = OhSnap::IdentitySerializer # Should inherit from OhSnap::BaseSerializer and implement #serialize.
178
+ config.snapshots_dirname = '__snapshots__' # Sets the name for the directory used to store snapshots.
179
+ config.writable = false # Sets the write/update mode. `true` if the UPDATE_SNAPSHOTS environment variable is set.
180
+ end
181
+ ```
182
+
183
+ ### Write your own serializer
184
+
185
+ Using `IdentitySerializer` forces you to compare stringified values to snapshots.
186
+ If you need to serialize non-string objects, you can write your own serializer inheriting from `OhSnap::BaseSerializer` and implementing the `#serialize` method to return a string.
187
+
188
+ ```ruby
189
+ require 'json'
190
+
191
+ class MyJSONSerializer < OhSnap::BaseSerializer
192
+ def serialize(value)
193
+ JSON.dump(value)
194
+ end
195
+ end
196
+ ```
197
+
198
+ You can then use `setup_snapshots` in your test to use your new serializer:
199
+
200
+ ```ruby
201
+ def setup
202
+ setup_snapshots(serializer: MyJSONSerializer)
203
+ end
204
+
205
+ def test_it_renders_the_expected_json_body
206
+ json_value = render_json_value
207
+ assert_equal snapshot, json_value
208
+ end
209
+ ```
210
+
211
+ ## Contributing
212
+
213
+ Bug reports and pull requests are welcome on GitHub at https://github.com/migl/oh_snap.
214
+
215
+ ## License
216
+
217
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'snapshot'
4
+
5
+ module OhSnap
6
+ # Serializes and loads Snapshot objects.
7
+ class BaseSerializer
8
+ def serialize(value)
9
+ raise NotImplementedError, "#{self.class} must implement #serialize"
10
+ end
11
+
12
+ def load(value)
13
+ Snapshot.new(value, serializer: self)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oh_snap'
4
+ require 'oh_snap/registry'
5
+
6
+ module OhSnap
7
+ # Helper methods for OhSnap to be included in test suites.
8
+ module Helper
9
+ def setup_snapshots(path:,
10
+ serializer: OhSnap.config.serializer.new,
11
+ snapshots_dirname: OhSnap.config.snapshots_dirname,
12
+ writable: OhSnap.config.writable?)
13
+ @ohsnapshots = Registry.new(path: path,
14
+ serializer: serializer,
15
+ snapshots_dirname: snapshots_dirname,
16
+ writable: writable)
17
+ end
18
+
19
+ def snapshot(name:)
20
+ @ohsnapshots.snapshot(name: name)
21
+ end
22
+
23
+ def snapshot_matches(actual, name:)
24
+ snapshot(name: name) == actual
25
+ end
26
+
27
+ def save_snapshots
28
+ @ohsnapshots&.save
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_serializer'
4
+
5
+ module OhSnap
6
+ # Returns `value` as is, without any transformation.
7
+ # `value` must be writable to a file.
8
+ class IdentitySerializer < BaseSerializer
9
+ def serialize(value)
10
+ value
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oh_snap/helper'
4
+
5
+ module OhSnap
6
+ # OhSnap helper for Minitest.
7
+ module Minitest
8
+ include OhSnap::Helper
9
+
10
+ def before_setup
11
+ super
12
+ setup_snapshots(path: File.dirname(File.expand_path(method(name).source_location.first)))
13
+ end
14
+
15
+ def snapshot(name: nil)
16
+ super(name: name || self.name)
17
+ end
18
+
19
+ def after_teardown
20
+ save_snapshots
21
+ super
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OhSnap
4
+ # Registry is responsible for managing snapshots during a test run.
5
+ class Registry
6
+ def initialize(path:, serializer:, snapshots_dirname:, writable:)
7
+ @path = path
8
+ @serializer = serializer
9
+ @snapshots_dirname = snapshots_dirname
10
+ @writable = writable
11
+ @snapshots = {}
12
+ end
13
+
14
+ def snapshot(name:)
15
+ return @snapshots[name] if @snapshots&.key?(name)
16
+
17
+ unless File.exist?(snapshot_path(name))
18
+ return @snapshots[name] = @serializer.load('') unless writable?
19
+
20
+ return @snapshots[name] = @serializer.load(nil)
21
+ end
22
+
23
+ File.open(snapshot_path(name), 'r') do |file|
24
+ @snapshots[name] = @serializer.load(file.read)
25
+ end
26
+
27
+ @snapshots[name]
28
+ end
29
+
30
+ def save
31
+ return unless writable?
32
+
33
+ Dir.mkdir(snapshots_path) unless Dir.exist?(snapshots_path)
34
+
35
+ @snapshots.each do |name, snapshot|
36
+ dump(name, snapshot)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def writable?
43
+ @writable
44
+ end
45
+
46
+ def dump(name, snapshot)
47
+ value = snapshot.touched? ? snapshot.actual : snapshot.expected
48
+ return if value.nil? || value.empty?
49
+
50
+ File.open(snapshot_path(name), 'w') do |file|
51
+ file.write(value)
52
+ end
53
+ end
54
+
55
+ def snapshot_path(name)
56
+ File.join(snapshots_path, "#{name}.snap")
57
+ end
58
+
59
+ def snapshots_path
60
+ @snapshots_path ||= File.join(@path, @snapshots_dirname)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oh_snap/helper'
4
+
5
+ module OhSnap
6
+ # OhSnap helper for RSpec.
7
+ module RSpec
8
+ include OhSnap::Helper
9
+
10
+ def self.included(rspec)
11
+ rspec.before do
12
+ setup_snapshots(path: File.dirname(File.expand_path(rspec.location)))
13
+ end
14
+
15
+ rspec.after do
16
+ save_snapshots
17
+ end
18
+ end
19
+
20
+ def snapshot(name: nil)
21
+ name ||= ::RSpec.current_example
22
+ .full_description
23
+ .tr("%$|/:;<>?*#\"\t\r\n\\", '-')
24
+ .tr(' ', '_')
25
+ .strip
26
+ super(name: name)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OhSnap
4
+ # Represents a snapshot
5
+ class Snapshot
6
+ attr_reader :actual, :expected, :matched
7
+
8
+ def initialize(expected = nil, serializer:)
9
+ @actual = nil
10
+ @expected = expected
11
+ @serializer = serializer
12
+ end
13
+
14
+ def ==(other)
15
+ @actual = serialize(other)
16
+ @expected ||= serialize(other)
17
+ @matched = @expected == @actual
18
+ end
19
+
20
+ def touched?
21
+ defined?(@matched) && !@matched
22
+ end
23
+
24
+ def inspect
25
+ @expected.inspect
26
+ end
27
+
28
+ private
29
+
30
+ def serialize(value)
31
+ @serializer.serialize(value)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OhSnap
4
+ VERSION = '0.2.3'
5
+ end
data/lib/oh_snap.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oh_snap/identity_serializer'
4
+
5
+ # OhSnap is a library for managing snapshots in tests.
6
+ # In memory of Laurent "Lolo" Wauquier, who never missed an opportunity to say it.
7
+ module OhSnap
8
+ # Configuration class for OhSnap.
9
+ Configuration = Struct.new(:serializer, :snapshots_dirname, :writable) do
10
+ alias_method :writable?, :writable
11
+ end
12
+
13
+ def self.config
14
+ @config ||= Configuration.new.tap do |config|
15
+ config.serializer = OhSnap::IdentitySerializer
16
+ config.snapshots_dirname = '__snapshots__'
17
+ config.writable = ENV.fetch('UPDATE_SNAPSHOTS', false)
18
+ end
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oh_snap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - migl
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bundler
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ description: OhSnap is a Ruby library that provides snapshot testing capabilities
27
+ for Ruby applications, allowing developers to easily capture and compare snapshots
28
+ of serializable structures.
29
+ email: hello@migl.dev
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE.txt
35
+ - README.md
36
+ - lib/oh_snap.rb
37
+ - lib/oh_snap/base_serializer.rb
38
+ - lib/oh_snap/helper.rb
39
+ - lib/oh_snap/identity_serializer.rb
40
+ - lib/oh_snap/minitest.rb
41
+ - lib/oh_snap/registry.rb
42
+ - lib/oh_snap/rspec.rb
43
+ - lib/oh_snap/snapshot.rb
44
+ - lib/oh_snap/version.rb
45
+ homepage: https://github.com/migl/oh_snap
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ github_repo: ssh://github.com/migl/oh_snap
50
+ source_code_uri: https://github.com/migl/oh_snap
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: 3.0.0
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.6.7
66
+ specification_version: 4
67
+ summary: Snapshots for Ruby tests
68
+ test_files: []