saharspec 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 69a3d4d52b75ffe4622413b927337a741b810c57
4
- data.tar.gz: 7da973ac28887c0fbd3c65a7aaad6bb6a5140e63
3
+ metadata.gz: a363bdb21050423225b98a8dae3d2f8861947426
4
+ data.tar.gz: 3ace51cf8d457d79caa8fe2c3b12e144c1a5a50a
5
5
  SHA512:
6
- metadata.gz: 02fb0e99a0b46ace2311deed7d21132582c11f8d5c04727d211f28d5725615eed36ce9e942ec7a31a9767b23b137918eac650598869ed6e01fce79bf503ab43a
7
- data.tar.gz: b112f9ea6892c954de423ce07076c530f46c66549d2201d06fe4233b18f933a6505110f148ca4b14ad139e357c0ca3ef26fb106437749d9f23ca584f7be6c987
6
+ metadata.gz: a6b1f390d030eeebbaa9ece4efbc64176e24cfd32d748e044a1d47fd8f7862b5a966a83fdcb64474b28496f0d9ea97150e70d725c0b99d9ed47d74d26d4f60b2
7
+ data.tar.gz: fbf40cbc81e264aeb3f78d14c5c934a3c501e3586e851c32718e4cd2be9eaedd58f5f3e2fc5c4664159bb1abec90b065fb907589bde3395a4b62ae89083d8180
data/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Saharspec history
2
+
3
+ ## 0.0.3 -- 2017-11-06
4
+
5
+ * Introduce new `its`-family addition: `its_call(*args)`
6
+ ```ruby
7
+ let(:array) { %i[a b c] }
8
+ subject { array.method(:delete_at) }
9
+ its_call(1) { is_expected.to change(array, :length).by(-1) }
10
+ ```
11
+ * Introduce new `ret` matcher ("block is expected to return"), useful in context when block is your
12
+ primary subject:
13
+
14
+ ```ruby
15
+ let(:array) { %i[a b c] }
16
+ subject { array.method(:delete_at) }
17
+ its_call(1) { is_expected.to ret(:b).and change(array, :length).by(-1) }
18
+ ```
19
+ * Introduce experimental `dont` matcher combiner (instead of `RSpec.define_negated_matcher`), used
20
+ as `expect { something }.to dont.change { anything }`;
21
+ * **Breaking**: rename `its_call`→`its_block` (because the name was needed for other means).
22
+
23
+ ## 0.0.2 -- 2017-09-01
24
+
25
+ * Make `send_message` composable;
26
+ * Add `Util#multiline`.
27
+
28
+ ## 0.0.1 -- 2017-08-14
29
+
30
+ Initial!
data/README.md CHANGED
@@ -50,6 +50,58 @@ its_call { is_expected.to send_message(Net::HTTP, :get).with('http://google.com'
50
50
  Note: there is [reasons](https://github.com/rspec/rspec-expectations/issues/934) why it is not in rspec-mocks, though, not very persuative for
51
51
  me.
52
52
 
53
+ #### `expect { block }.to ret(value)` matcher
54
+
55
+ Checks whether `#call`-able subject (block, method, command object), when called, return value matching
56
+ to expected.
57
+
58
+ Useful when this callable subject is your primary one:
59
+
60
+ ```ruby
61
+ # before: option 1. subject is value
62
+ subject { 2 + x }
63
+
64
+ context 'when numeric' do
65
+ let(:x) { 3 }
66
+ it { is_expected.to eq 5 } # DRY
67
+ end
68
+
69
+ context 'when incompatible' do
70
+ let(:x) { '3' }
71
+ it { expect { subject }.to raise_error } # not DRY
72
+ end
73
+
74
+ # option 2. subject is block
75
+ subject { -> {2 + x } }
76
+
77
+ context 'when incompatible' do
78
+ let(:x) { '3' }
79
+ it { is_expected.to raise_error } # DRY
80
+ end
81
+
82
+ context 'when numeric' do
83
+ let(:x) { 3 }
84
+ it { expect(subject.call).to eq 5 } # not DRY
85
+ end
86
+
87
+ # after
88
+ require 'saharspec/matchers/ret'
89
+
90
+ subject { -> { 2 + x } }
91
+
92
+ context 'when numeric' do
93
+ let(:x) { 3 }
94
+ it { is_expected.to ret 5 } # DRY: notice `ret`
95
+ end
96
+
97
+ context 'when incompatible' do
98
+ let(:x) { '3' }
99
+ it { is_expected.to raise_error } # DRY
100
+ end
101
+ ```
102
+
103
+ Plays really well with `its_call` shown below.
104
+
53
105
  #### `eq_multiline(text)` matcher
54
106
 
55
107
  Dedicated to checking some multiline text generators.
@@ -81,6 +133,23 @@ require 'saharspec/matchers/eq_multiline'
81
133
  ```
82
134
  (empty lines before/after are removed, text deindented up to `|` sign)
83
135
 
136
+ ### `dont`: matcher negation
137
+
138
+ Another (exprimental) attempt to get rid of `define_negated_matcher`. `dont` is not 100% grammatically
139
+ correct, yet short and readable enought. It just negates attached matcher.
140
+
141
+ ```ruby
142
+ # before
143
+ RSpec.define_negated_matcher :not_change, :change
144
+
145
+ it { expect { code }.to do_stuff.and not_change(obj, :attr) }
146
+
147
+ # after: no `define_negated_matcher` needed
148
+ require 'saharspec/matchers/dont'
149
+
150
+ it { expect { code }.to do_stuff.and dont.change(obj, :attr) }
151
+ ```
152
+
84
153
  ### `its`-addons
85
154
 
86
155
  **Notice**: There are different opinions on usability/reasonability of `its(:attribute)` syntax,
@@ -104,18 +173,59 @@ require 'saharspec/its/map'
104
173
  its_map(:text) { are_expected.to all not_be_empty }
105
174
  ```
106
175
 
107
- #### `its_call`
176
+ #### `its_block`
177
+
178
+ Allows to DRY-ly refer to "block that calculates subject".
108
179
 
109
180
  ```ruby
110
181
  subject { some_operation_that_may_fail }
111
182
 
112
183
  # before
113
- it { expect { subject }.to raise_error(...) }
184
+ context 'success' do
185
+ it { is_expected.to eq 123 }
186
+ end
187
+
188
+ context 'fail' do
189
+ it { expect { subject }.to raise_error(...) }
190
+ end
191
+
192
+ # after
193
+ require 'saharspec/its/block'
194
+
195
+ its_block { is_expected.to raise_error(...) }
196
+ ```
197
+
198
+ #### `its_call`
199
+
200
+ Allows to DRY-ly test callable object with different arguments. Plays well with forementioned `ret`
201
+ matcher.
202
+
203
+ Before:
204
+
205
+ ```ruby
206
+ # before
207
+ describe '#delete_at' do
208
+ let(:array) { %i[a b c] }
209
+
210
+ it { expect(array.delete_at(1) }.to eq :b }
211
+ it { expect(array.delete_at(8) }.to eq nil }
212
+ it { expect { array.delete_at(1) }.to change(array, :length).by(-1) }
213
+ it { expect { array.delete_at(:b) }.to raise_error TypeError }
214
+ end
114
215
 
115
216
  # after
116
217
  require 'saharspec/its/call'
117
218
 
118
- its_call { is_expected.to raise_error(...) }
219
+ describe '#delete_at' do
220
+ let(:array) { %i[a b c] }
221
+
222
+ subject { array.method(:delete_at) }
223
+
224
+ its_call(1) { is_expected.to ret :b }
225
+ its_call(1) { is_expected.to change(array, :length).by(-1) }
226
+ its_call(8) { is_expected.to ret nil }
227
+ its_call(:b) { is_expected.to raise_error TypeError }
228
+ end
119
229
  ```
120
230
 
121
231
  ## State & future
data/lib/saharspec/its.rb CHANGED
@@ -1,10 +1,15 @@
1
1
  module Saharspec
2
2
  # Wrapper module for all `its_*` RSpec additions.
3
3
  #
4
- # See {Map#its_map #its_map} and {Call#its_call #its_call}
4
+ # See:
5
+ #
6
+ # * {Map#its_map #its_map}
7
+ # * {Block#its_block #its_block}
8
+ # * {Call#its_call #its_call}
5
9
  module Its
6
10
  end
7
11
  end
8
12
 
9
13
  require_relative 'its/map'
14
+ require_relative 'its/block'
10
15
  require_relative 'its/call'
@@ -0,0 +1,56 @@
1
+ module Saharspec
2
+ module Its
3
+ module Block
4
+ # Creates nested example which converts current subject to a block-subject.
5
+ #
6
+ # @example
7
+ #
8
+ # subject { calc_something(params) }
9
+ #
10
+ # # without its_block
11
+ # context 'with this params' do
12
+ # it { expect { subject }.to change(some, :value).by(1) }
13
+ # end
14
+ #
15
+ # context 'with that params' do
16
+ # it { expect { subject }.to raise_error(SomeError) }
17
+ # end
18
+ #
19
+ # # with its_block
20
+ # context 'with this params' do
21
+ # its_block { is_expected.to change(some, :value).by(1) }
22
+ # end
23
+ #
24
+ # context 'with that params' do
25
+ # its_block { is_expected.to raise_error(SomeError) }
26
+ # end
27
+ #
28
+ # @param options Options (metadata) that can be passed to usual RSpec example.
29
+ # @param block [Proc] The test itself. Inside it, `is_expected` is a synonom
30
+ # for `expect { subject }`.
31
+ #
32
+ def its_block(*options, &block)
33
+ # rubocop:disable Lint/NestedMethodDefinition
34
+ describe('as block') do
35
+ let(:__call_subject) do
36
+ -> { subject }
37
+ end
38
+
39
+ def is_expected
40
+ expect(__call_subject)
41
+ end
42
+
43
+ example(nil, *options, &block)
44
+ end
45
+ # rubocop:enable Lint/NestedMethodDefinition
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ RSpec.configure do |rspec|
52
+ rspec.extend Saharspec::Its::Block
53
+ rspec.backtrace_exclusion_patterns << %r{/lib/saharspec/its/block}
54
+ end
55
+
56
+ RSpec::SharedContext.send(:include, Saharspec::Its::Block)
@@ -1,46 +1,41 @@
1
1
  module Saharspec
2
2
  module Its
3
3
  module Call
4
- # Creates nested example which converts current subject to a block-subject.
4
+ # For `#call`-able subject, creates nested example where subject is called with arguments
5
+ # provided, allowing to apply block matchers like `.to change(something)` or `.to raise_error`
6
+ # to different calls in a DRY way.
5
7
  #
6
- # @example
7
- #
8
- # subject { calc_something(params) }
8
+ # Also, plays really well with {RSpec::Matchers#ret #ret} block matcher.
9
9
  #
10
- # # without its_call
11
- # context 'with this params'
12
- # it { expect { subject }.to change(some, :value).by(1) }
13
- # end
10
+ # @example
11
+ # let(:array) { %i[a b c] }
14
12
  #
15
- # context 'with that params'
16
- # it { expect { subject }.to raise_error(SomeError) }
17
- # end
13
+ # describe '#[]' do
14
+ # subject { array.method(:[]) }
18
15
  #
19
- # # with its_call
20
- # context 'with this params'
21
- # its_call { is_expected.to change(some, :value).by(1) }
16
+ # its_call(1) { is_expected.to ret :b }
17
+ # its_call(1..-1) { is_expected.to ret %i[b c] }
18
+ # its_call('foo') { is_expected.to raise_error TypeError }
22
19
  # end
23
20
  #
24
- # context 'with that params'
25
- # its_call { is_expected.to raise_error(SomeError) }
21
+ # describe '#push' do
22
+ # subject { array.method(:push) }
23
+ # its_call(5) { is_expected.to change(array, :length).by(1) }
26
24
  # end
27
25
  #
28
- # @param options Other options that can be passed to usual RSpec example.
29
- # @param block [Proc] The test itself. Inside it, `is_expected` (or `are_expected`) is analog of
30
- # `expect { subject }`.
31
- #
32
- def its_call(*options, &block)
26
+ def its_call(*args, &block)
33
27
  # rubocop:disable Lint/NestedMethodDefinition
34
28
  describe('call') do
35
29
  let(:__call_subject) do
36
- -> { subject }
30
+ warn 'No need to use its_call without arguments, just it {} will work' if args.empty?
31
+ -> { subject.call(*args) }
37
32
  end
38
33
 
39
34
  def is_expected
40
35
  expect(__call_subject)
41
36
  end
42
37
 
43
- example(nil, *options, &block)
38
+ example(nil, &block)
44
39
  end
45
40
  # rubocop:enable Lint/NestedMethodDefinition
46
41
  end
@@ -13,7 +13,7 @@ module Saharspec
13
13
  # # with attribute chain
14
14
  # its_map(:'reverse.upcase') { is_expected.to eq %w[TSET EM ESAELP] }
15
15
  #
16
- # # with Hash (or any other object re sponding to `#[]`)
16
+ # # with Hash (or any other object responding to `#[]`)
17
17
  # subject {
18
18
  # [
19
19
  # {title: 'Slaughterhouse Five', author: {first: 'Kurt', last: 'Vonnegut'}},
@@ -1,7 +1,12 @@
1
1
  module Saharspec
2
2
  # All Saharspec matchers, when required, included into `RSpec::Matchers` namespace.
3
3
  #
4
- # See {RSpec::Matchers#send_message #send_message} and {RSpec::Matchers#eq_multiline #eq_multiline}.
4
+ # See:
5
+ #
6
+ # * {RSpec::Matchers#dont #dont}
7
+ # * {RSpec::Matchers#send_message #send_message}
8
+ # * {RSpec::Matchers#eq_multiline #eq_multiline}
9
+ # * {RSpec::Matchers#ret #ret}
5
10
  #
6
11
  module Matchers
7
12
  end
@@ -9,3 +14,4 @@ end
9
14
 
10
15
  require_relative 'matchers/eq_multiline'
11
16
  require_relative 'matchers/send_message'
17
+ require_relative 'matchers/ret'
@@ -0,0 +1,66 @@
1
+ module Saharspec
2
+ module Matchers
3
+ # @private
4
+ class Not < RSpec::Matchers::BuiltIn::BaseMatcher
5
+ def initialize
6
+ @delegator = Delegator.new
7
+ end
8
+
9
+ def description
10
+ "not #{@matcher.description}"
11
+ end
12
+
13
+ def match(_expected, actual)
14
+ @matcher or
15
+ fail(ArgumentError, '`dont` matcher used without any matcher to negate. Usage: dont.other_matcher(args)')
16
+ !@matcher.matches?(actual)
17
+ end
18
+
19
+ def supports_block_expectations?
20
+ @matcher.supports_block_expectations?
21
+ end
22
+
23
+ def method_missing(m, *a, &b) # rubocop:disable Style/MethodMissing
24
+ if @matcher
25
+ @matcher.send(m, *a, &b)
26
+ else
27
+ @matcher = @delegator.send(m, *a, &b)
28
+ end
29
+
30
+ self
31
+ end
32
+
33
+ def respond_to_missing?(method, include_private = false)
34
+ if @matcher
35
+ @matcher.respond_to?(method, include_private)
36
+ else
37
+ @delegator.respond_to_missing?(method, include_private)
38
+ end
39
+ end
40
+
41
+ class Delegator
42
+ include RSpec::Matchers
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module RSpec
49
+ module Matchers
50
+ # Negates attached matcher, allowing creating negated matchers on the fly.
51
+ #
52
+ # While not being 100% grammatically correct, seems to be readable enough.
53
+ #
54
+ # @example
55
+ # # before
56
+ # RSpec.define_negated_matcher :not_change, :change
57
+ # it { expect { code }.to do_stuff.and not_change(obj, :attr) }
58
+ #
59
+ # # after: no `define_negated_matcher` needed
60
+ # it { expect { code }.to do_stuff.and dont.change(obj, :attr) }
61
+ #
62
+ def dont
63
+ Saharspec::Matchers::Not.new
64
+ end
65
+ end
66
+ end
@@ -32,7 +32,7 @@ module RSpec
32
32
  # |end
33
33
  # })
34
34
  #
35
- # @param [String]
35
+ # @param expected [String]
36
36
  def eq_multiline(expected)
37
37
  Saharspec::Matchers::EqMultiline.new(expected)
38
38
  end
@@ -0,0 +1,82 @@
1
+ module Saharspec
2
+ module Matchers
3
+ # @private
4
+ class Ret
5
+ include RSpec::Matchers::Composable
6
+
7
+ def initialize(expected)
8
+ @expected = expected
9
+ end
10
+
11
+ def matches?(subject)
12
+ @subject = subject
13
+ return false unless subject.respond_to?(:call)
14
+ @actual = subject.call
15
+ @expected === @actual # rubocop:disable Style/CaseEquality
16
+ end
17
+
18
+ def supports_block_expectations?
19
+ true
20
+ end
21
+
22
+ def description
23
+ "return #{@expected.respond_to?(:description) ? @expected.description : @expected.inspect}"
24
+ end
25
+
26
+ def failure_message
27
+ "expected to #{description}, " +
28
+ (@subject.respond_to?(:call) ? "but returned #{@actual.inspect}" : 'but was not callable')
29
+ end
30
+
31
+ def failure_message_when_negated
32
+ "expected not to #{description}, but returned it"
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ module RSpec
39
+ module Matchers
40
+ # `ret` (short for `return`) checks if provided block **returns** value specified.
41
+ #
42
+ # It should be considered instead of simple value matchers (like `eq`) in the situations:
43
+ #
44
+ # 1. Several block behaviors tested in the same test, joined with `.and`, or in separate tests
45
+ # 2. You test what some block or method returns with arguments, using {Call#its_call #its_call}
46
+ #
47
+ # Values are tested with `===`, which allows chaining other matchers and patterns to the check.
48
+ #
49
+ # @note
50
+ # There is a case when `ret` fails: when it is _not the first_ in a chain of matchers joined
51
+ # by `.and`. That's not exactly the matchers bug, that's how RSpec works (loses block's return
52
+ # value passing the block between matchers)
53
+ #
54
+ # @example
55
+ # # case 1: block is a subject
56
+ # subject { -> { do_something } }
57
+ #
58
+ # it { is_expected.not_to raise_error }
59
+ # it { is_expected.to change(some, :value).by(1) }
60
+ # it { is_expected.to ret 8 }
61
+ #
62
+ # # or, joined:
63
+ # specify {
64
+ # expect { do_something }.to ret(8).and change(some, :value).by(1)
65
+ # }
66
+ #
67
+ # # case 2: with arguments
68
+ # subject { %i[a b c].method(:[]) }
69
+ #
70
+ # its_call(1) { is_expected.to ret :b }
71
+ # its_call(1..-1) { is_expected.to ret %i[b c] }
72
+ # its_call('foo') { is_expected.to raise_error TypeError }
73
+ #
74
+ # # Note, that values are tested with ===, which means all other matchers could be chained:
75
+ # its_call(1) { is_expected.to ret instance_of(Symbol) }
76
+ # its_call(1..-1) { is_expected.to ret instance_of(Array).and have_attributes(length: 2) }
77
+ #
78
+ def ret(expected)
79
+ Saharspec::Matchers::Ret.new(expected)
80
+ end
81
+ end
82
+ end
@@ -32,7 +32,7 @@ module Saharspec
32
32
  end
33
33
 
34
34
  def times
35
- raise NoMethodError unless @times
35
+ fail NoMethodError unless @times
36
36
  self
37
37
  end
38
38
 
@@ -44,6 +44,11 @@ module Saharspec
44
44
  exactly(2)
45
45
  end
46
46
 
47
+ def ordered
48
+ @ordered = true
49
+ self
50
+ end
51
+
47
52
  # Matching
48
53
  def matches?(subject)
49
54
  run(subject)
@@ -78,8 +83,8 @@ module Saharspec
78
83
 
79
84
  def run(subject)
80
85
  @target.respond_to?(@method, true) or
81
- raise NoMethodError,
82
- "undefined method `#{@method}' for#{@target.inspect}:#{@target.class}"
86
+ fail NoMethodError,
87
+ "undefined method `#{@method}' for#{@target.inspect}:#{@target.class}"
83
88
  allow(@target).to allower
84
89
  subject.call
85
90
  end
@@ -95,6 +100,7 @@ module Saharspec
95
100
  have_received(@method).tap do |e|
96
101
  e.with(*@arguments) if @arguments
97
102
  e.exactly(@times).times if @times
103
+ e.ordered if @ordered
98
104
  end
99
105
  end
100
106
  end
@@ -115,12 +121,14 @@ module RSpec
115
121
  #
116
122
  # # after:
117
123
  # require 'saharspec/matchers/send_message'
124
+ #
118
125
  # it { expect { code_being_tested }.to send_message(double, :fetch).with(something) }
119
- # # after + its_call
120
- # require 'saharspec/its/call'
121
- # subject { code_being_tested }
122
- # its_call { is_expected.to send_message(double, :fetch).with(something) }
123
126
  #
127
+ # # after + its_block
128
+ # require 'saharspec/its/block'
129
+ #
130
+ # subject { code_being_tested }
131
+ # its_block { is_expected.to send_message(double, :fetch).with(something) }
124
132
  #
125
133
  # @param target Object which expects message, double or real object
126
134
  # @param method [Symbol] Message being expected
@@ -0,0 +1,9 @@
1
+ module Saharspec
2
+ # @private
3
+ MAJOR = 0
4
+ # @private
5
+ MINOR = 0
6
+ # @private
7
+ PATCH = 3
8
+ VERSION = [MAJOR, MINOR, PATCH].join('.')
9
+ end
data/saharspec.gemspec CHANGED
@@ -1,6 +1,8 @@
1
+ require_relative 'lib/saharspec/version'
2
+
1
3
  Gem::Specification.new do |s|
2
4
  s.name = 'saharspec'
3
- s.version = '0.0.2'
5
+ s.version = Saharspec::VERSION
4
6
  s.authors = ['Victor Shepelev']
5
7
  s.email = 'zverok.offline@gmail.com'
6
8
  s.homepage = 'https://github.com/zverok/saharspec'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saharspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Shepelev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-01 00:00:00.000000000 Z
11
+ date: 2017-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -116,17 +116,22 @@ extra_rdoc_files: []
116
116
  files:
117
117
  - ".rubocop_todo.yml"
118
118
  - ".yardopts"
119
+ - CHANGELOG.md
119
120
  - LICENSE.txt
120
121
  - README.md
121
122
  - lib/saharspec.rb
122
123
  - lib/saharspec/its.rb
124
+ - lib/saharspec/its/block.rb
123
125
  - lib/saharspec/its/call.rb
124
126
  - lib/saharspec/its/map.rb
125
127
  - lib/saharspec/matchers.rb
128
+ - lib/saharspec/matchers/dont.rb
126
129
  - lib/saharspec/matchers/eq_multiline.rb
127
130
  - lib/saharspec/matchers/request_webmock.rb
131
+ - lib/saharspec/matchers/ret.rb
128
132
  - lib/saharspec/matchers/send_message.rb
129
133
  - lib/saharspec/util.rb
134
+ - lib/saharspec/version.rb
130
135
  - saharspec.gemspec
131
136
  homepage: https://github.com/zverok/saharspec
132
137
  licenses: