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 +4 -4
- data/DEVELOPMENT.md +1 -0
- data/README.md +56 -0
- data/lib/smartest/fixture.rb +50 -0
- data/lib/smartest/simple_stub.rb +178 -0
- data/lib/smartest/version.rb +1 -1
- data/lib/smartest.rb +1 -0
- data/smartest/simple_stub_test.rb +337 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46365bf842594d74023e6c47da5b98f28e2f8ad765a035de09a54f5d127db2ee
|
|
4
|
+
data.tar.gz: 53134bcd0052d5cd8b495b36a9ba8bb1344c6dcb0626d0547dfa49bf6eff005e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5ffe755697e79babe4e54e52d9a2e2a13aee1ade7306dfa7adffd7d9ea6ca75b7b96825b05c713261f420f35b28e980a85b068a94823109bffc0a54774d864a
|
|
7
|
+
data.tar.gz: c030cd6bfcfb44f8273348f99a36a6213c237c9bd580cd49287351b4bf9b70a04af3b6b7889287f12ffbda747dfd7d6a450b9954bf51f8c2bdf5fb71768027ba
|
data/DEVELOPMENT.md
CHANGED
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`
|
data/lib/smartest/fixture.rb
CHANGED
|
@@ -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
|
data/lib/smartest/version.rb
CHANGED
data/lib/smartest.rb
CHANGED
|
@@ -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.
|
|
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:
|