smartest 0.3.3.alpha1 → 0.3.3.alpha3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e7e25cbb3432e62d6264a3308b1b8f01406ed44362f742356b31aa31629926a
4
- data.tar.gz: cbb07c4ae5e9532836d505543f1af85ea222fd013d65b3fa79d0de0228fac1e6
3
+ metadata.gz: 46365bf842594d74023e6c47da5b98f28e2f8ad765a035de09a54f5d127db2ee
4
+ data.tar.gz: 53134bcd0052d5cd8b495b36a9ba8bb1344c6dcb0626d0547dfa49bf6eff005e
5
5
  SHA512:
6
- metadata.gz: 5d8cc3fde5695e83649858389212a849ded911ff350689747a62a53ae4c0ce7da5c800e8d6866fb46b04756d0f890efaa3107e9e0bed642092cf2f42aa01bb63
7
- data.tar.gz: dbd5c2b3fab3d0ab182c78d2ee8f60dbb71efe61e0779646bea2194d1e2710bc3baf6c307b046500dcb293446bcd49a1a1c119c09df22703a9805f83992b7780
6
+ metadata.gz: a5ffe755697e79babe4e54e52d9a2e2a13aee1ade7306dfa7adffd7d9ea6ca75b7b96825b05c713261f420f35b28e980a85b068a94823109bffc0a54774d864a
7
+ data.tar.gz: c030cd6bfcfb44f8273348f99a36a6213c237c9bd580cd49287351b4bf9b70a04af3b6b7889287f12ffbda747dfd7d6a450b9954bf51f8c2bdf5fb71768027ba
data/DEVELOPMENT.md CHANGED
@@ -45,6 +45,7 @@ smartest/
45
45
  expectations.rb
46
46
  expectation_target.rb
47
47
  matchers.rb
48
+ simple_stub.rb
48
49
 
49
50
  runner.rb
50
51
  test_result.rb
data/README.md CHANGED
@@ -600,6 +600,61 @@ end
600
600
 
601
601
  Register cleanup immediately after acquiring the resource, before later setup steps that may fail.
602
602
 
603
+ ## Stubs
604
+
605
+ Use simple stub helpers when a fixture needs to temporarily replace a Ruby
606
+ method and reset it during cleanup:
607
+
608
+ ```ruby
609
+ class PaymentFixture < Smartest::Fixture
610
+ fixture :payment_gateway_stub do
611
+ simple_stub_any_instance_of(PaymentGateway, :charge) { :approved }
612
+ end
613
+ end
614
+ ```
615
+
616
+ Register the fixture class from `around_suite` before tests request the fixture:
617
+
618
+ ```ruby
619
+ around_suite do |suite|
620
+ use_fixture PaymentFixture
621
+ suite.run
622
+ end
623
+ ```
624
+
625
+ `use_fixture` is available inside `around_suite` or `around_test` blocks, not as
626
+ a top-level method in a test file.
627
+
628
+ The stub affects existing instances and new instances of the target class in
629
+ the current Fiber until it is reset. Other Fibers and Threads continue to see
630
+ the original method unless they apply their own stub. Tests can request the
631
+ fixture to make the side effect explicit:
632
+
633
+ ```ruby
634
+ test("checkout succeeds") do |payment_gateway_stub:|
635
+ expect(Checkout.call).to eq(:paid)
636
+ end
637
+ ```
638
+
639
+ Use `simple_stub(Time, :now) { fixed_time }` for singleton methods such as class
640
+ methods. Use `simple_stub_const("AppConfig::PAYMENT_PROVIDER", "fake")` for
641
+ constants. Constant stubs are process-global; avoid concurrent tests that stub
642
+ the same constant.
643
+
644
+ The method stub helpers call `Smartest::SimpleStub` internally, apply the stub,
645
+ register `cleanup { stub.reset }`, and return the stub object.
646
+ `simple_stub_const` records the previous constant value, replaces it, and
647
+ restores or removes it during cleanup. These helpers are available inside
648
+ `Smartest::Fixture` fixture blocks because they need cleanup to tie the stub
649
+ lifetime to the fixture scope.
650
+
651
+ `Smartest::SimpleStub#apply` and `#reset` are idempotent in the current Fiber.
652
+ `apply!` raises
653
+ `Smartest::SimpleStub::AlreadyAppliedError` when the stub is already active in
654
+ the current Fiber, and `reset!` raises
655
+ `Smartest::SimpleStub::NotAppliedError` when it is not active there. See
656
+ [Stubs](documentation/docs/stubs.md).
657
+
603
658
  ## Logged-in client example
604
659
 
605
660
  ```ruby
@@ -825,6 +880,7 @@ Smartest currently focuses on a small runner API:
825
880
  - fixture dependencies through keyword arguments
826
881
  - fixture cleanup
827
882
  - suite-scoped fixtures through `suite_fixture`
883
+ - fixture-scoped method and constant stubs
828
884
  - suite hooks with `around_suite`
829
885
  - test hooks with `around_test`
830
886
  - skipped and pending tests through `skip` and `pending`
@@ -65,6 +65,56 @@ module Smartest
65
65
  @fixture_set.add_cleanup(&block)
66
66
  end
67
67
 
68
+ def simple_stub_any_instance_of(klass, method_name, &block)
69
+ apply_simple_stub(SimpleStub.new(klass, method_name, &block))
70
+ end
71
+
72
+ def simple_stub(object, method_name, &block)
73
+ apply_simple_stub(SimpleStub.new(object.singleton_class, method_name, &block))
74
+ end
75
+
76
+ def simple_stub_const(constant_path, value)
77
+ owner, constant_name = resolve_simple_stub_constant(constant_path)
78
+ original_defined = owner.const_defined?(constant_name, false)
79
+ original_value = owner.const_get(constant_name, false) if original_defined
80
+
81
+ owner.__send__(:remove_const, constant_name) if original_defined
82
+ owner.const_set(constant_name, value)
83
+
84
+ cleanup do
85
+ owner.__send__(:remove_const, constant_name) if owner.const_defined?(constant_name, false)
86
+ owner.const_set(constant_name, original_value) if original_defined
87
+ end
88
+
89
+ value
90
+ end
91
+
92
+ def apply_simple_stub(stub)
93
+ stub.apply!
94
+ cleanup { stub.reset }
95
+ stub
96
+ end
97
+
98
+ def resolve_simple_stub_constant(constant_path)
99
+ unless constant_path.is_a?(String) || constant_path.is_a?(Symbol)
100
+ raise ArgumentError, "constant path must be a String or Symbol"
101
+ end
102
+
103
+ names = constant_path.to_s.split("::")
104
+ names.shift if names.first == ""
105
+ raise ArgumentError, "constant path must not be empty" if names.empty?
106
+
107
+ names.each do |name|
108
+ raise ArgumentError, "invalid constant path: #{constant_path}" unless name.match?(/\A[A-Z]\w*\z/)
109
+ end
110
+
111
+ constant_name = names.pop.to_sym
112
+ owner = names.reduce(Object) { |namespace, name| namespace.const_get(name, false) }
113
+ raise ArgumentError, "constant owner must be a Module or Class" unless owner.is_a?(Module)
114
+
115
+ [owner, constant_name]
116
+ end
117
+
68
118
  def method_missing(method_name, *args, &block)
69
119
  return super if RESERVED_CONTEXT_METHODS.include?(method_name)
70
120
 
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Smartest
6
+ class SimpleStub
7
+ STORAGE_KEY = :__smartest_simple_stub
8
+
9
+ class AlreadyAppliedError < Smartest::Error; end
10
+ class NotAppliedError < Smartest::Error; end
11
+
12
+ class << self
13
+ def implementation_for(klass_key, method_name)
14
+ current_stubs&.fetch(stub_key(klass_key, method_name), nil)
15
+ end
16
+
17
+ def active_stubs
18
+ Thread.current[STORAGE_KEY] ||= {}
19
+ end
20
+
21
+ def clear_active_stubs_if_empty
22
+ Thread.current[STORAGE_KEY] = nil if current_stubs&.empty?
23
+ end
24
+
25
+ def ensure_dispatcher_method(klass, klass_key, method_name)
26
+ dispatcher_mutex.synchronize do
27
+ mod = dispatcher_module_for(klass, klass_key)
28
+ next if mod.method_defined?(method_name) || mod.private_method_defined?(method_name)
29
+
30
+ mod.define_method(method_name) do |*args, **kwargs, &block|
31
+ implementation = SimpleStub.implementation_for(klass_key, method_name)
32
+
33
+ if implementation
34
+ SimpleStub.call_implementation(self, implementation, args, kwargs, block)
35
+ elsif kwargs.empty?
36
+ super(*args, &block)
37
+ else
38
+ super(*args, **kwargs, &block)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def stub_key(klass_key, method_name)
45
+ [klass_key, method_name]
46
+ end
47
+
48
+ def current_stubs
49
+ Thread.current[STORAGE_KEY]
50
+ end
51
+
52
+ def call_implementation(receiver, implementation, args, kwargs, block)
53
+ return call_implementation_with_block(receiver, implementation, args, kwargs, block) if block
54
+
55
+ if kwargs.empty?
56
+ receiver.instance_exec(*args, &implementation)
57
+ else
58
+ receiver.instance_exec(*args, **kwargs, &implementation)
59
+ end
60
+ end
61
+
62
+ def call_implementation_with_block(receiver, implementation, args, kwargs, block)
63
+ method_name = next_call_method_name
64
+ singleton_class = receiver.singleton_class
65
+ singleton_class.__send__(:define_method, method_name, &implementation)
66
+
67
+ if kwargs.empty?
68
+ receiver.__send__(method_name, *args, &block)
69
+ else
70
+ receiver.__send__(method_name, *args, **kwargs, &block)
71
+ end
72
+ ensure
73
+ if singleton_class &&
74
+ (singleton_class.method_defined?(method_name) || singleton_class.private_method_defined?(method_name))
75
+ singleton_class.__send__(:remove_method, method_name)
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def dispatcher_module_for(klass, klass_key)
82
+ const_name = dispatcher_module_name(klass_key)
83
+
84
+ if const_defined?(const_name, false)
85
+ const_get(const_name, false)
86
+ else
87
+ Module.new.tap do |mod|
88
+ const_set(const_name, mod)
89
+ klass.prepend(mod)
90
+ end
91
+ end
92
+ end
93
+
94
+ def dispatcher_module_name(klass_key)
95
+ "StubDispatcher#{klass_key}"
96
+ end
97
+
98
+ def dispatcher_mutex
99
+ @dispatcher_mutex ||= Mutex.new
100
+ end
101
+
102
+ def next_call_method_name
103
+ call_mutex.synchronize do
104
+ @call_sequence ||= 0
105
+ @call_sequence += 1
106
+ :"__smartest_simple_stub_call_#{@call_sequence}"
107
+ end
108
+ end
109
+
110
+ def call_mutex
111
+ @call_mutex ||= Mutex.new
112
+ end
113
+ end
114
+
115
+ def initialize(klass, method_name, &implementation)
116
+ raise ArgumentError, "klass must be a Class. #{klass.class} specified." unless klass.is_a?(Class)
117
+ raise ArgumentError, "method name must be a Symbol." unless method_name.is_a?(Symbol)
118
+
119
+ @klass = klass
120
+ @method_name = method_name
121
+ @implementation = implementation
122
+ end
123
+
124
+ def apply
125
+ return if stub_defined?
126
+
127
+ apply_stub
128
+ end
129
+
130
+ def apply!
131
+ raise AlreadyAppliedError, "stub for #{@klass}##{@method_name} is already applied" if stub_defined?
132
+
133
+ apply_stub
134
+ end
135
+
136
+ def reset
137
+ return unless stub_defined?
138
+
139
+ reset_stub
140
+ end
141
+
142
+ def reset!
143
+ raise NotAppliedError, "stub for #{@klass}##{@method_name} is not applied" unless stub_defined?
144
+
145
+ reset_stub
146
+ end
147
+
148
+ private
149
+
150
+ def apply_stub
151
+ raise ArgumentError, "block must be given for applying stub" unless @implementation
152
+
153
+ self.class.ensure_dispatcher_method(@klass, klass_key, @method_name)
154
+ active_stubs[stub_key] = @implementation
155
+ end
156
+
157
+ def reset_stub
158
+ active_stubs.delete(stub_key)
159
+ self.class.clear_active_stubs_if_empty
160
+ end
161
+
162
+ def active_stubs
163
+ self.class.active_stubs
164
+ end
165
+
166
+ def stub_key
167
+ self.class.stub_key(klass_key, @method_name)
168
+ end
169
+
170
+ def klass_key
171
+ @klass_key ||= Digest::SHA256.hexdigest(@klass.object_id.to_s)
172
+ end
173
+
174
+ def stub_defined?
175
+ self.class.current_stubs&.key?(stub_key)
176
+ end
177
+ end
178
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smartest
4
- VERSION = "0.3.3.alpha1"
4
+ VERSION = "0.3.3.alpha3"
5
5
  end
data/lib/smartest.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "smartest/version"
4
4
  require_relative "smartest/errors"
5
+ require_relative "smartest/simple_stub"
5
6
  require_relative "smartest/parameter_extractor"
6
7
  require_relative "smartest/test_case"
7
8
  require_relative "smartest/test_registry"
@@ -0,0 +1,337 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+
5
+ require "smartest/autorun"
6
+ require "stringio"
7
+
8
+ module SimpleStubSelfTest
9
+ module_function
10
+
11
+ def test_case(name, block)
12
+ Smartest::TestCase.new(
13
+ name: name,
14
+ metadata: {},
15
+ block: block,
16
+ location: caller_locations(1, 1).first
17
+ )
18
+ end
19
+
20
+ def run_suite(suite)
21
+ output = StringIO.new
22
+ status = Smartest::Runner.new(suite: suite, reporter: Smartest::Reporter.new(output)).run
23
+
24
+ [status, output.string]
25
+ end
26
+
27
+ def capture_error(expected_error)
28
+ yield
29
+ rescue Exception => error
30
+ raise if Smartest.fatal_exception?(error)
31
+
32
+ unless error.is_a?(expected_error)
33
+ raise Smartest::AssertionFailed, "expected #{expected_error}, but raised #{error.class}: #{error.message}"
34
+ end
35
+
36
+ error
37
+ else
38
+ raise Smartest::AssertionFailed, "expected #{expected_error}, but nothing was raised"
39
+ end
40
+ end
41
+
42
+ class SimpleStubSelfTestSubject
43
+ def initialize(name)
44
+ @name = name
45
+ end
46
+
47
+ def name
48
+ "original #{@name}"
49
+ end
50
+
51
+ def greeting(prefix)
52
+ "#{prefix}, #{@name}"
53
+ end
54
+
55
+ def yielding_greeting(prefix)
56
+ yield "#{prefix}, #{@name}"
57
+ end
58
+ end
59
+
60
+ class SimpleStubSelfTestClock
61
+ def self.now
62
+ :original_now
63
+ end
64
+ end
65
+
66
+ module SimpleStubSelfTestConfig
67
+ PROVIDER = :original_provider
68
+ end
69
+
70
+ test("simple stub stubs instance methods until reset") do
71
+ existing = SimpleStubSelfTestSubject.new("Alice")
72
+ stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed" }
73
+
74
+ stub.apply!
75
+
76
+ begin
77
+ expect(existing.name).to eq("stubbed")
78
+ expect(SimpleStubSelfTestSubject.new("Bob").name).to eq("stubbed")
79
+ ensure
80
+ stub.reset
81
+ end
82
+
83
+ expect(existing.name).to eq("original Alice")
84
+ expect(SimpleStubSelfTestSubject.new("Bob").name).to eq("original Bob")
85
+ end
86
+
87
+ test("simple stub can be reset from a fresh stub object") do
88
+ Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :greeting) { |prefix| "#{prefix}, stubbed" }.apply!
89
+
90
+ begin
91
+ expect(SimpleStubSelfTestSubject.new("Alice").greeting("Hi")).to eq("Hi, stubbed")
92
+ ensure
93
+ Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :greeting).reset!
94
+ end
95
+
96
+ expect(SimpleStubSelfTestSubject.new("Alice").greeting("Hi")).to eq("Hi, Alice")
97
+ end
98
+
99
+ test("simple_stub_any_instance_of applies and resets from fixture cleanup") do
100
+ fixture_class = Class.new(Smartest::Fixture) do
101
+ fixture :stubbed_name do
102
+ simple_stub_any_instance_of(SimpleStubSelfTestSubject, :name) { "fixture #{@name}" }
103
+ :stubbed_name
104
+ end
105
+ end
106
+
107
+ suite = Smartest::Suite.new
108
+ suite.fixture_classes.add(fixture_class)
109
+ suite.tests.add(
110
+ SimpleStubSelfTest.test_case(
111
+ "uses instance stub fixture",
112
+ proc do |stubbed_name:|
113
+ expect(stubbed_name).to eq(:stubbed_name)
114
+ expect(SimpleStubSelfTestSubject.new("Alice").name).to eq("fixture Alice")
115
+ end
116
+ )
117
+ )
118
+ suite.tests.add(
119
+ SimpleStubSelfTest.test_case(
120
+ "sees reset instance method",
121
+ proc { expect(SimpleStubSelfTestSubject.new("Alice").name).to eq("original Alice") }
122
+ )
123
+ )
124
+
125
+ status, = SimpleStubSelfTest.run_suite(suite)
126
+
127
+ expect(status).to eq(0)
128
+ end
129
+
130
+ test("simple_stub applies and resets singleton methods from fixture cleanup") do
131
+ fixture_class = Class.new(Smartest::Fixture) do
132
+ fixture :stubbed_time do
133
+ simple_stub(SimpleStubSelfTestClock, :now) { :stubbed_now }
134
+ :stubbed_time
135
+ end
136
+ end
137
+
138
+ suite = Smartest::Suite.new
139
+ suite.fixture_classes.add(fixture_class)
140
+ suite.tests.add(
141
+ SimpleStubSelfTest.test_case(
142
+ "uses singleton stub fixture",
143
+ proc do |stubbed_time:|
144
+ expect(stubbed_time).to eq(:stubbed_time)
145
+ expect(SimpleStubSelfTestClock.now).to eq(:stubbed_now)
146
+ end
147
+ )
148
+ )
149
+ suite.tests.add(
150
+ SimpleStubSelfTest.test_case(
151
+ "sees reset singleton method",
152
+ proc { expect(SimpleStubSelfTestClock.now).to eq(:original_now) }
153
+ )
154
+ )
155
+
156
+ status, = SimpleStubSelfTest.run_suite(suite)
157
+
158
+ expect(status).to eq(0)
159
+ end
160
+
161
+ test("simple_stub_const applies and resets existing constants from fixture cleanup") do
162
+ fixture_class = Class.new(Smartest::Fixture) do
163
+ fixture :stubbed_provider do
164
+ simple_stub_const("SimpleStubSelfTestConfig::PROVIDER", :stubbed_provider)
165
+ end
166
+ end
167
+
168
+ suite = Smartest::Suite.new
169
+ suite.fixture_classes.add(fixture_class)
170
+ suite.tests.add(
171
+ SimpleStubSelfTest.test_case(
172
+ "uses constant stub fixture",
173
+ proc do |stubbed_provider:|
174
+ expect(stubbed_provider).to eq(:stubbed_provider)
175
+ expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:stubbed_provider)
176
+ end
177
+ )
178
+ )
179
+ suite.tests.add(
180
+ SimpleStubSelfTest.test_case(
181
+ "sees restored constant",
182
+ proc { expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:original_provider) }
183
+ )
184
+ )
185
+
186
+ status, = SimpleStubSelfTest.run_suite(suite)
187
+
188
+ expect(status).to eq(0)
189
+ end
190
+
191
+ test("simple_stub_const removes newly defined constants from fixture cleanup") do
192
+ fixture_class = Class.new(Smartest::Fixture) do
193
+ fixture :stubbed_missing_provider do
194
+ simple_stub_const("SimpleStubSelfTestConfig::MISSING_PROVIDER", :stubbed_missing_provider)
195
+ end
196
+ end
197
+
198
+ suite = Smartest::Suite.new
199
+ suite.fixture_classes.add(fixture_class)
200
+ suite.tests.add(
201
+ SimpleStubSelfTest.test_case(
202
+ "uses new constant stub fixture",
203
+ proc do |stubbed_missing_provider:|
204
+ expect(stubbed_missing_provider).to eq(:stubbed_missing_provider)
205
+ expect(SimpleStubSelfTestConfig::MISSING_PROVIDER).to eq(:stubbed_missing_provider)
206
+ end
207
+ )
208
+ )
209
+ suite.tests.add(
210
+ SimpleStubSelfTest.test_case(
211
+ "sees removed constant",
212
+ proc { expect(SimpleStubSelfTestConfig.const_defined?(:MISSING_PROVIDER, false)).to eq(false) }
213
+ )
214
+ )
215
+
216
+ status, = SimpleStubSelfTest.run_suite(suite)
217
+
218
+ expect(status).to eq(0)
219
+ end
220
+
221
+ test("simple stub preserves receiver self and method blocks") do
222
+ subject = SimpleStubSelfTestSubject.new("Alice")
223
+ stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :yielding_greeting) do |prefix, &block|
224
+ block.call("#{prefix}, stubbed #{@name}")
225
+ end
226
+
227
+ stub.apply!
228
+
229
+ begin
230
+ result = subject.yielding_greeting("Hi") { |message| message.upcase }
231
+ expect(result).to eq("HI, STUBBED ALICE")
232
+ ensure
233
+ stub.reset
234
+ end
235
+
236
+ expect(subject.yielding_greeting("Hi") { |message| message }).to eq("Hi, Alice")
237
+ end
238
+
239
+ test("simple stub is scoped to the current fiber") do
240
+ subject = SimpleStubSelfTestSubject.new("Alice")
241
+ stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed #{@name}" }
242
+
243
+ stub.apply!
244
+
245
+ begin
246
+ expect(subject.name).to eq("stubbed Alice")
247
+ Fiber.new do
248
+ expect(subject.name).to eq("original Alice")
249
+ end.resume
250
+ ensure
251
+ stub.reset
252
+ end
253
+
254
+ expect(subject.name).to eq("original Alice")
255
+ end
256
+
257
+ test("simple stub can differ per thread") do
258
+ subject = SimpleStubSelfTestSubject.new("Alice")
259
+ queue = Queue.new
260
+ main_stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "main #{@name}" }
261
+
262
+ main_stub.apply!
263
+
264
+ thread = Thread.new do
265
+ thread_stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "thread #{@name}" }
266
+ thread_stub.apply!
267
+
268
+ begin
269
+ queue << subject.name
270
+ rescue Exception => error
271
+ queue << error
272
+ ensure
273
+ thread_stub.reset
274
+ end
275
+ end
276
+
277
+ begin
278
+ expect(subject.name).to eq("main Alice")
279
+
280
+ thread_result = queue.pop
281
+ raise thread_result if thread_result.is_a?(Exception)
282
+
283
+ expect(thread_result).to eq("thread Alice")
284
+ thread.join
285
+ expect(subject.name).to eq("main Alice")
286
+ ensure
287
+ main_stub.reset
288
+ thread.kill if thread.alive?
289
+ end
290
+
291
+ expect(subject.name).to eq("original Alice")
292
+ end
293
+
294
+ test("simple stub supports safe and strict apply reset APIs") do
295
+ stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed" }
296
+
297
+ stub.apply
298
+ stub.apply
299
+
300
+ begin
301
+ error = SimpleStubSelfTest.capture_error(Smartest::SimpleStub::AlreadyAppliedError) do
302
+ Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "other" }.apply!
303
+ end
304
+
305
+ expect(error.message).to eq("stub for SimpleStubSelfTestSubject#name is already applied")
306
+ ensure
307
+ stub.reset
308
+ end
309
+
310
+ stub.reset
311
+
312
+ error = SimpleStubSelfTest.capture_error(Smartest::SimpleStub::NotAppliedError) do
313
+ stub.reset!
314
+ end
315
+
316
+ expect(error.message).to eq("stub for SimpleStubSelfTestSubject#name is not applied")
317
+ end
318
+
319
+ test("simple stub validates constructor arguments and apply block") do
320
+ error = SimpleStubSelfTest.capture_error(ArgumentError) do
321
+ Smartest::SimpleStub.new(Object.new, :name)
322
+ end
323
+
324
+ expect(error.message).to eq("klass must be a Class. Object specified.")
325
+
326
+ error = SimpleStubSelfTest.capture_error(ArgumentError) do
327
+ Smartest::SimpleStub.new(SimpleStubSelfTestSubject, "name")
328
+ end
329
+
330
+ expect(error.message).to eq("method name must be a Symbol.")
331
+
332
+ error = SimpleStubSelfTest.capture_error(ArgumentError) do
333
+ Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name).apply!
334
+ end
335
+
336
+ expect(error.message).to eq("block must be given for applying stub")
337
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3.alpha1
4
+ version: 0.3.3.alpha3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Iwaki
@@ -62,6 +62,7 @@ files:
62
62
  - lib/smartest/parameter_extractor.rb
63
63
  - lib/smartest/reporter.rb
64
64
  - lib/smartest/runner.rb
65
+ - lib/smartest/simple_stub.rb
65
66
  - lib/smartest/suite.rb
66
67
  - lib/smartest/suite_run.rb
67
68
  - lib/smartest/test_case.rb
@@ -71,6 +72,7 @@ files:
71
72
  - lib/smartest/test_run_state.rb
72
73
  - lib/smartest/version.rb
73
74
  - smartest.gemspec
75
+ - smartest/simple_stub_test.rb
74
76
  - smartest/smartest_test.rb
75
77
  homepage: https://smartest-rb.vercel.app
76
78
  licenses: