smartest 0.3.2 → 0.3.3.alpha2
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 +4 -4
- data/DEVELOPMENT.md +1 -0
- data/README.md +40 -0
- data/exe/smartest +1 -1
- data/lib/smartest/autorun.rb +1 -1
- data/lib/smartest/fixture.rb +14 -0
- data/lib/smartest/simple_stub.rb +178 -0
- data/lib/smartest/version.rb +1 -1
- data/lib/smartest.rb +16 -0
- data/smartest/simple_stub_test.rb +273 -0
- data/smartest.gemspec +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 396af151370001b4762c6c1a71426fef7c2e0773626080af62496bc068afdc02
|
|
4
|
+
data.tar.gz: 0b5b6e9e76582dfd91df63fe7e2928fbbd6d0a90101e8f3d9834a91d7b94c109
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 320397233b8b2e0c46b5d44d6a8bc892d44bfccfc9b63a1db31f868fdc3ac7da7c500902f93b36124f61119dc23b05a3238d3032d7e814defed8c7f5b57f9ef1
|
|
7
|
+
data.tar.gz: 0b507415bf8e31472e4117371474226fbec62802be8a41d002cc38403967d2a877e60d2a7fb0d0695490e416e1fcf208928f3a6882620208c4d694f425138b37
|
data/DEVELOPMENT.md
CHANGED
data/README.md
CHANGED
|
@@ -65,6 +65,8 @@ end
|
|
|
65
65
|
|
|
66
66
|
## Installation
|
|
67
67
|
|
|
68
|
+
Smartest requires Ruby 2.7 or newer.
|
|
69
|
+
|
|
68
70
|
Add this line to your application's Gemfile:
|
|
69
71
|
|
|
70
72
|
```ruby
|
|
@@ -598,6 +600,43 @@ end
|
|
|
598
600
|
|
|
599
601
|
Register cleanup immediately after acquiring the resource, before later setup steps that may fail.
|
|
600
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
|
+
The stub affects existing instances and new instances of the target class in
|
|
617
|
+
the current Fiber until it is reset. Other Fibers and Threads continue to see
|
|
618
|
+
the original method unless they apply their own stub. Tests can request the
|
|
619
|
+
fixture to make the side effect explicit:
|
|
620
|
+
|
|
621
|
+
```ruby
|
|
622
|
+
test("checkout succeeds") do |payment_gateway_stub:|
|
|
623
|
+
expect(Checkout.call).to eq(:paid)
|
|
624
|
+
end
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Use `simple_stub(Time, :now) { fixed_time }` for singleton methods such as class
|
|
628
|
+
methods. Both helpers call `Smartest::SimpleStub` internally, apply the stub,
|
|
629
|
+
register `cleanup { stub.reset }`, and return the stub object. They are
|
|
630
|
+
available inside `Smartest::Fixture` fixture blocks because they need cleanup to
|
|
631
|
+
tie the stub lifetime to the fixture scope.
|
|
632
|
+
|
|
633
|
+
`Smartest::SimpleStub#apply` and `#reset` are idempotent in the current Fiber.
|
|
634
|
+
`apply!` raises
|
|
635
|
+
`Smartest::SimpleStub::AlreadyAppliedError` when the stub is already active in
|
|
636
|
+
the current Fiber, and `reset!` raises
|
|
637
|
+
`Smartest::SimpleStub::NotAppliedError` when it is not active there. See
|
|
638
|
+
[Stubs](documentation/docs/stubs.md).
|
|
639
|
+
|
|
601
640
|
## Logged-in client example
|
|
602
641
|
|
|
603
642
|
```ruby
|
|
@@ -823,6 +862,7 @@ Smartest currently focuses on a small runner API:
|
|
|
823
862
|
- fixture dependencies through keyword arguments
|
|
824
863
|
- fixture cleanup
|
|
825
864
|
- suite-scoped fixtures through `suite_fixture`
|
|
865
|
+
- fixture-scoped method stubs through `simple_stub_any_instance_of` and `simple_stub`
|
|
826
866
|
- suite hooks with `around_suite`
|
|
827
867
|
- test hooks with `around_test`
|
|
828
868
|
- skipped and pending tests through `skip` and `pending`
|
data/exe/smartest
CHANGED
|
@@ -43,7 +43,7 @@ begin
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
Smartest.disable_autorun!
|
|
46
|
-
|
|
46
|
+
Smartest.install_dsl!
|
|
47
47
|
test_load_path = File.expand_path("smartest", Dir.pwd)
|
|
48
48
|
$LOAD_PATH.unshift(test_load_path) if Dir.exist?(test_load_path) && !$LOAD_PATH.include?(test_load_path)
|
|
49
49
|
|
data/lib/smartest/autorun.rb
CHANGED
data/lib/smartest/fixture.rb
CHANGED
|
@@ -65,6 +65,20 @@ 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 apply_simple_stub(stub)
|
|
77
|
+
stub.apply!
|
|
78
|
+
cleanup { stub.reset }
|
|
79
|
+
stub
|
|
80
|
+
end
|
|
81
|
+
|
|
68
82
|
def method_missing(method_name, *args, &block)
|
|
69
83
|
return super if RESERVED_CONTEXT_METHODS.include?(method_name)
|
|
70
84
|
|
|
@@ -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
|
data/lib/smartest/version.rb
CHANGED
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"
|
|
@@ -57,11 +58,26 @@ module Smartest
|
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
61
|
+
def install_dsl!
|
|
62
|
+
if kernel_prepend_effective?
|
|
63
|
+
Kernel.prepend DSL
|
|
64
|
+
else
|
|
65
|
+
Object.include DSL
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
60
69
|
def fatal_exception?(error)
|
|
61
70
|
error.is_a?(SystemExit) ||
|
|
62
71
|
error.is_a?(Interrupt) ||
|
|
63
72
|
error.is_a?(SignalException) ||
|
|
64
73
|
error.is_a?(NoMemoryError)
|
|
65
74
|
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def kernel_prepend_effective?
|
|
79
|
+
# Ruby 2.7 does not support Kernel.prepend for defining top-level method.
|
|
80
|
+
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0")
|
|
81
|
+
end
|
|
66
82
|
end
|
|
67
83
|
end
|
|
@@ -0,0 +1,273 @@
|
|
|
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
|
+
test("simple stub stubs instance methods until reset") do
|
|
67
|
+
existing = SimpleStubSelfTestSubject.new("Alice")
|
|
68
|
+
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed" }
|
|
69
|
+
|
|
70
|
+
stub.apply!
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
expect(existing.name).to eq("stubbed")
|
|
74
|
+
expect(SimpleStubSelfTestSubject.new("Bob").name).to eq("stubbed")
|
|
75
|
+
ensure
|
|
76
|
+
stub.reset
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
expect(existing.name).to eq("original Alice")
|
|
80
|
+
expect(SimpleStubSelfTestSubject.new("Bob").name).to eq("original Bob")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
test("simple stub can be reset from a fresh stub object") do
|
|
84
|
+
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :greeting) { |prefix| "#{prefix}, stubbed" }.apply!
|
|
85
|
+
|
|
86
|
+
begin
|
|
87
|
+
expect(SimpleStubSelfTestSubject.new("Alice").greeting("Hi")).to eq("Hi, stubbed")
|
|
88
|
+
ensure
|
|
89
|
+
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :greeting).reset!
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
expect(SimpleStubSelfTestSubject.new("Alice").greeting("Hi")).to eq("Hi, Alice")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
test("simple_stub_any_instance_of applies and resets from fixture cleanup") do
|
|
96
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
97
|
+
fixture :stubbed_name do
|
|
98
|
+
simple_stub_any_instance_of(SimpleStubSelfTestSubject, :name) { "fixture #{@name}" }
|
|
99
|
+
:stubbed_name
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
suite = Smartest::Suite.new
|
|
104
|
+
suite.fixture_classes.add(fixture_class)
|
|
105
|
+
suite.tests.add(
|
|
106
|
+
SimpleStubSelfTest.test_case(
|
|
107
|
+
"uses instance stub fixture",
|
|
108
|
+
proc do |stubbed_name:|
|
|
109
|
+
expect(stubbed_name).to eq(:stubbed_name)
|
|
110
|
+
expect(SimpleStubSelfTestSubject.new("Alice").name).to eq("fixture Alice")
|
|
111
|
+
end
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
suite.tests.add(
|
|
115
|
+
SimpleStubSelfTest.test_case(
|
|
116
|
+
"sees reset instance method",
|
|
117
|
+
proc { expect(SimpleStubSelfTestSubject.new("Alice").name).to eq("original Alice") }
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
status, = SimpleStubSelfTest.run_suite(suite)
|
|
122
|
+
|
|
123
|
+
expect(status).to eq(0)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
test("simple_stub applies and resets singleton methods from fixture cleanup") do
|
|
127
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
128
|
+
fixture :stubbed_time do
|
|
129
|
+
simple_stub(SimpleStubSelfTestClock, :now) { :stubbed_now }
|
|
130
|
+
:stubbed_time
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
suite = Smartest::Suite.new
|
|
135
|
+
suite.fixture_classes.add(fixture_class)
|
|
136
|
+
suite.tests.add(
|
|
137
|
+
SimpleStubSelfTest.test_case(
|
|
138
|
+
"uses singleton stub fixture",
|
|
139
|
+
proc do |stubbed_time:|
|
|
140
|
+
expect(stubbed_time).to eq(:stubbed_time)
|
|
141
|
+
expect(SimpleStubSelfTestClock.now).to eq(:stubbed_now)
|
|
142
|
+
end
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
suite.tests.add(
|
|
146
|
+
SimpleStubSelfTest.test_case(
|
|
147
|
+
"sees reset singleton method",
|
|
148
|
+
proc { expect(SimpleStubSelfTestClock.now).to eq(:original_now) }
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
status, = SimpleStubSelfTest.run_suite(suite)
|
|
153
|
+
|
|
154
|
+
expect(status).to eq(0)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
test("simple stub preserves receiver self and method blocks") do
|
|
158
|
+
subject = SimpleStubSelfTestSubject.new("Alice")
|
|
159
|
+
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :yielding_greeting) do |prefix, &block|
|
|
160
|
+
block.call("#{prefix}, stubbed #{@name}")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
stub.apply!
|
|
164
|
+
|
|
165
|
+
begin
|
|
166
|
+
result = subject.yielding_greeting("Hi") { |message| message.upcase }
|
|
167
|
+
expect(result).to eq("HI, STUBBED ALICE")
|
|
168
|
+
ensure
|
|
169
|
+
stub.reset
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
expect(subject.yielding_greeting("Hi") { |message| message }).to eq("Hi, Alice")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
test("simple stub is scoped to the current fiber") do
|
|
176
|
+
subject = SimpleStubSelfTestSubject.new("Alice")
|
|
177
|
+
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed #{@name}" }
|
|
178
|
+
|
|
179
|
+
stub.apply!
|
|
180
|
+
|
|
181
|
+
begin
|
|
182
|
+
expect(subject.name).to eq("stubbed Alice")
|
|
183
|
+
Fiber.new do
|
|
184
|
+
expect(subject.name).to eq("original Alice")
|
|
185
|
+
end.resume
|
|
186
|
+
ensure
|
|
187
|
+
stub.reset
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
expect(subject.name).to eq("original Alice")
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
test("simple stub can differ per thread") do
|
|
194
|
+
subject = SimpleStubSelfTestSubject.new("Alice")
|
|
195
|
+
queue = Queue.new
|
|
196
|
+
main_stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "main #{@name}" }
|
|
197
|
+
|
|
198
|
+
main_stub.apply!
|
|
199
|
+
|
|
200
|
+
thread = Thread.new do
|
|
201
|
+
thread_stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "thread #{@name}" }
|
|
202
|
+
thread_stub.apply!
|
|
203
|
+
|
|
204
|
+
begin
|
|
205
|
+
queue << subject.name
|
|
206
|
+
rescue Exception => error
|
|
207
|
+
queue << error
|
|
208
|
+
ensure
|
|
209
|
+
thread_stub.reset
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
begin
|
|
214
|
+
expect(subject.name).to eq("main Alice")
|
|
215
|
+
|
|
216
|
+
thread_result = queue.pop
|
|
217
|
+
raise thread_result if thread_result.is_a?(Exception)
|
|
218
|
+
|
|
219
|
+
expect(thread_result).to eq("thread Alice")
|
|
220
|
+
thread.join
|
|
221
|
+
expect(subject.name).to eq("main Alice")
|
|
222
|
+
ensure
|
|
223
|
+
main_stub.reset
|
|
224
|
+
thread.kill if thread.alive?
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
expect(subject.name).to eq("original Alice")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
test("simple stub supports safe and strict apply reset APIs") do
|
|
231
|
+
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed" }
|
|
232
|
+
|
|
233
|
+
stub.apply
|
|
234
|
+
stub.apply
|
|
235
|
+
|
|
236
|
+
begin
|
|
237
|
+
error = SimpleStubSelfTest.capture_error(Smartest::SimpleStub::AlreadyAppliedError) do
|
|
238
|
+
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "other" }.apply!
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
expect(error.message).to eq("stub for SimpleStubSelfTestSubject#name is already applied")
|
|
242
|
+
ensure
|
|
243
|
+
stub.reset
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
stub.reset
|
|
247
|
+
|
|
248
|
+
error = SimpleStubSelfTest.capture_error(Smartest::SimpleStub::NotAppliedError) do
|
|
249
|
+
stub.reset!
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
expect(error.message).to eq("stub for SimpleStubSelfTestSubject#name is not applied")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
test("simple stub validates constructor arguments and apply block") do
|
|
256
|
+
error = SimpleStubSelfTest.capture_error(ArgumentError) do
|
|
257
|
+
Smartest::SimpleStub.new(Object.new, :name)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
expect(error.message).to eq("klass must be a Class. Object specified.")
|
|
261
|
+
|
|
262
|
+
error = SimpleStubSelfTest.capture_error(ArgumentError) do
|
|
263
|
+
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, "name")
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
expect(error.message).to eq("method name must be a Symbol.")
|
|
267
|
+
|
|
268
|
+
error = SimpleStubSelfTest.capture_error(ArgumentError) do
|
|
269
|
+
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name).apply!
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
expect(error.message).to eq("block must be given for applying stub")
|
|
273
|
+
end
|
data/smartest.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.description = "Smartest is a Ruby test runner with pytest-style fixture injection. Tests request fixtures using keyword arguments, fixtures can depend on other fixtures, and cleanup is registered only when needed."
|
|
12
12
|
spec.homepage = "https://smartest-rb.vercel.app"
|
|
13
13
|
spec.license = "MIT"
|
|
14
|
-
spec.required_ruby_version = ">=
|
|
14
|
+
spec.required_ruby_version = ">= 2.7"
|
|
15
15
|
|
|
16
16
|
spec.metadata = {
|
|
17
17
|
"allowed_push_host" => "https://rubygems.org",
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smartest
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.3.alpha2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yusuke Iwaki
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -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:
|
|
@@ -91,12 +93,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
91
93
|
requirements:
|
|
92
94
|
- - ">="
|
|
93
95
|
- !ruby/object:Gem::Version
|
|
94
|
-
version: '
|
|
96
|
+
version: '2.7'
|
|
95
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
98
|
requirements:
|
|
97
|
-
- - "
|
|
99
|
+
- - ">"
|
|
98
100
|
- !ruby/object:Gem::Version
|
|
99
|
-
version:
|
|
101
|
+
version: 1.3.1
|
|
100
102
|
requirements: []
|
|
101
103
|
rubygems_version: 3.4.19
|
|
102
104
|
signing_key:
|