arg-that 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -0
- data/README.md +33 -16
- data/lib/arg_that.rb +6 -0
- data/lib/arg_that/eqish.rb +21 -0
- data/lib/arg_that/version.rb +1 -1
- data/spec/arg_that_spec.rb +80 -41
- metadata +4 -2
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# arg-that
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/testdouble/arg-that.png?branch=master)](https://travis-ci.org/testdouble/arg-that)
|
4
|
+
|
3
5
|
arg-that provides a simple method to create an argument matcher in equality comparisons. This is particularly handy when writing a test to assert the equality of some complex data struct with another and only one component is difficult or unwise to assert exactly.
|
4
6
|
|
5
7
|
## wat?
|
@@ -19,11 +21,11 @@ Suppose our subject returns a hefty hash of attributes following a `save` operat
|
|
19
21
|
subject.save #=> {:name => "Bob", :age => 28, :email => "bob@example.com" :created_at => 2013-07-18 21:40:58 -0400}
|
20
22
|
```
|
21
23
|
|
22
|
-
While authoring the test, we neither care much about the value of `
|
24
|
+
While authoring the test, we neither care much about the value of `created_at`, nor do we know how to specify it exactly. That means we can't just do this:
|
23
25
|
|
24
26
|
``` ruby
|
25
27
|
result = subject.save
|
26
|
-
result.
|
28
|
+
expect(result).to eq(:name => "Bob", :age => 28, :email => "bob@example.com" :created_at => Time.new(2013,7,18,21,40,58,"-04:00"))
|
27
29
|
```
|
28
30
|
|
29
31
|
This wouldn't work, because at runtime the value of `created_at` will, of course, differ.
|
@@ -32,27 +34,27 @@ So, one could do this:
|
|
32
34
|
|
33
35
|
``` ruby
|
34
36
|
result = subject.save
|
35
|
-
result[:name].
|
36
|
-
result[:age].
|
37
|
-
result[:email].
|
37
|
+
expect(result[:name]).to eq("Bob")
|
38
|
+
expect(result[:age]).to eq(28)
|
39
|
+
expect(result[:email]).to eq("bob@example.com")
|
38
40
|
```
|
39
41
|
|
40
|
-
But now we've got three assertions when before we had one. Alas, we no longer have a clear visual of the *shape* of the data being returned by `save`. Additionally, if the map grows with additional meaningful values in the future, this test would continue to pass by incident.
|
42
|
+
But now we've got three assertions when before we only had one. Alas, we no longer have a clear visual of the *shape* of the data being returned by `save`. Additionally, if the map grows with additional meaningful values in the future, this test would continue to pass by incident.
|
41
43
|
|
42
44
|
The `arg_that` matcher can save us this annoyance by retaining the more terse *style* of the first example, while retaining the liberal specification necessitated by the situation:
|
43
45
|
|
44
46
|
```
|
45
|
-
result.
|
47
|
+
expect(result).to eqish(
|
46
48
|
:name => "Bob",
|
47
49
|
:age => 28,
|
48
50
|
:email => "bob@example.com",
|
49
51
|
:created_at => arg_that { true }
|
50
|
-
|
52
|
+
)
|
51
53
|
```
|
52
54
|
|
53
|
-
Where `arg_that { true }` would literally pass any equality test. If there's *something* we want to constrain about the `created_at` value, we could do so. Perhaps a type check like `arg_that { |arg| arg.kind_of?(Time) }` would be more appropriate.
|
55
|
+
Where `arg_that { true }` would literally pass any equality test. If there's *something* we want to constrain about the `created_at` value, we could do so. Perhaps a type check like `arg_that { |arg| arg.kind_of?(Time) }` would be more appropriate. Also, note that arg-that includes an RSpec matcher called `eqish` which is meant to be used in conjunction with the `arg_that` matcher. [Please refer to the bottom of this document for a discussion on why.]
|
54
56
|
|
55
|
-
The purpose of releasing something as simple as `arg-that` as a gem is to promote
|
57
|
+
The purpose of releasing something as simple as `arg-that` as a gem is to promote more intentionality about how specific any given equality assertion needs to be. The modus operandi of most Rubyists seems to be "always specify everything exactly, but if that gets hard, specify the remainder arbitrarily." And that's not terrific.
|
56
58
|
|
57
59
|
## usage
|
58
60
|
|
@@ -77,21 +79,36 @@ result = {
|
|
77
79
|
:last_audit => Time.new(2012, 8, 12)
|
78
80
|
}
|
79
81
|
|
80
|
-
result.
|
82
|
+
expect(result).to eqish(
|
81
83
|
:zip_code => 48176,
|
82
84
|
:owner => "Fred Jim",
|
83
85
|
:last_audit => arg_that { |arg| arg > Time.new(2012, 1, 1) }
|
84
|
-
|
86
|
+
)
|
85
87
|
```
|
86
88
|
|
87
89
|
In this way, the result will verify the two entries we want to specify exactly (`zip_code` and `owner`), but allows us the flexibility of only loosely specifying that we're okay with any value of `last_audit` so long as it was some time after January 1st, 2012.
|
88
90
|
|
89
|
-
##
|
91
|
+
## what's up with this `eqish` matcher?
|
92
|
+
|
93
|
+
**tl;dr whenever you use `arg_that` in an equality RSpec expectation, always use the `eqish` matcher or otherwise ensure that `==` is being called on the object containing the `arg_that` matcher**
|
94
|
+
|
95
|
+
As mentioned above, the reason that arg-that includes a matcher called `eqish` is because of the nature of how equality (`==`) tests work in Ruby (and most other OOP languages). The object that receives the message "are you equal?" is responsible for determining whether some other thing this equal to it.
|
90
96
|
|
91
|
-
|
97
|
+
This works fine in most of our programs, because in almost every circumstance, two objects of the same type will adhere to the *symmetric property of equality* contract when asked whether one equals the other.
|
92
98
|
|
93
|
-
|
99
|
+
That is to say, if:
|
94
100
|
|
95
101
|
``` ruby
|
96
|
-
|
102
|
+
x = 5
|
103
|
+
y = 5
|
104
|
+
|
105
|
+
x == y #=> true
|
106
|
+
y == x #=> true
|
97
107
|
```
|
108
|
+
|
109
|
+
**However**, it's the very nature of matchers like `arg_that` to *intentionally violate* the symmetric property of equality. We do this because such tests are only concerned about *partial equality*. As a result, to serve the purpose of the test, it's important that the expected value be the object who is asked "are you equal?" to the object being interrogated by the test; if the actual value is asked the question, then our definition of partial equality will never be invoked!
|
110
|
+
|
111
|
+
This is a bit of a bummer, because RSpec (and most testing libraries) will invoke `==` on the actual value, and not the expected value. Therefore, if an asymmetric definition of equality is desired, `==` must be invoked on the expected value.
|
112
|
+
|
113
|
+
To work around this, arg_that includes an RSpec matcher (which is auto-defined if you include `ArgThat` in an `RSpec.configure` block) called `eqish` [source](https://github.com/testdouble/arg-that/blob/master/lib/arg_that/eqish.rb). The implementation of `eqish` is literally to swap the order of `actual == expected` to `expected == actual`. In all other matters, it delegates to RSpec's built-in `eq` matcher.
|
114
|
+
|
data/lib/arg_that.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
RSpec::Matchers.define :eqish do |expected|
|
2
|
+
match do |actual|
|
3
|
+
expected == actual
|
4
|
+
end
|
5
|
+
|
6
|
+
failure_message_for_should do |actual|
|
7
|
+
RSpec::Matchers::BuiltIn::Eq.new(expected).
|
8
|
+
tap {|eq| eq.matches?(actual) }.
|
9
|
+
failure_message_for_should
|
10
|
+
end
|
11
|
+
|
12
|
+
failure_message_for_should_not do |actual|
|
13
|
+
RSpec::Matchers::BuiltIn::Eq.new(expected).
|
14
|
+
tap {|eq| eq.matches?(actual) }.
|
15
|
+
failure_message_for_should_not
|
16
|
+
end
|
17
|
+
|
18
|
+
description do
|
19
|
+
RSpec::Matchers::BuiltIn::Eq.new(expected).description
|
20
|
+
end
|
21
|
+
end
|
data/lib/arg_that/version.rb
CHANGED
data/spec/arg_that_spec.rb
CHANGED
@@ -8,57 +8,96 @@ RSpec.configure do |config|
|
|
8
8
|
end
|
9
9
|
|
10
10
|
describe ArgThat do
|
11
|
+
describe "using arg_that as a catch-all" do
|
12
|
+
subject { Object.new }
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
+
context "arg_that { true } always matches" do
|
15
|
+
Then { expect(subject).to eqish arg_that { true } }
|
16
|
+
end
|
14
17
|
|
15
|
-
|
16
|
-
|
18
|
+
context "arg_that { false } never matches" do
|
19
|
+
Then { expect(subject).to_not eqish arg_that { false } }
|
20
|
+
end
|
17
21
|
end
|
18
22
|
|
19
|
-
|
20
|
-
Then {
|
21
|
-
Then {
|
23
|
+
describe "directly comparing a value with an arg_that matcher" do
|
24
|
+
Then { expect(:foo).to eqish arg_that {|arg| arg.kind_of?(Symbol) } }
|
25
|
+
Then { expect(:foo).to_not eqish arg_that {|arg| arg.kind_of?(String) } }
|
22
26
|
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
{
|
29
|
-
:a => 1,
|
30
|
-
:b => 99
|
31
|
-
}.should == {
|
32
|
-
:a => 1,
|
33
|
-
:b => arg_that {|arg| arg > 98 && arg < 100 }
|
34
|
-
}
|
28
|
+
describe "comparing a value nestled in a collection with an arg_that matcher" do
|
29
|
+
context "arrays" do
|
30
|
+
Then { expect([5, 6, 1]).to eqish [5, 6, arg_that {|arg| arg < 2 }]}
|
31
|
+
Then { expect([5, 6, 1]).to_not eqish [5, 6, arg_that {|arg| arg > 1 }]}
|
35
32
|
end
|
36
33
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
34
|
+
context "hashes" do
|
35
|
+
Then do
|
36
|
+
expect(
|
37
|
+
:a => 1,
|
38
|
+
:b => 99
|
39
|
+
).to eqish(
|
40
|
+
:a => 1,
|
41
|
+
:b => arg_that {|arg| arg > 98 && arg < 100 }
|
42
|
+
)
|
43
|
+
end
|
48
44
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
45
|
+
Then do
|
46
|
+
expect(
|
47
|
+
:zip_code => 48176,
|
48
|
+
:owner => "Fred Jim",
|
49
|
+
:last_audit => Time.new(2012, 8, 12)
|
50
|
+
).to eqish(
|
51
|
+
:zip_code => 48176,
|
52
|
+
:owner => "Fred Jim",
|
53
|
+
:last_audit => arg_that { |arg| arg > Time.new(2012, 1, 1) }
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
Then do
|
58
|
+
expect(
|
59
|
+
:name => "Bob",
|
60
|
+
:age => 28,
|
61
|
+
:email => "bob@example.com",
|
62
|
+
:created_at => Time.new(2013, 7, 18, 21, 40, 58)
|
63
|
+
).to eqish(
|
64
|
+
:name => "Bob",
|
65
|
+
:age => 28,
|
66
|
+
:email => "bob@example.com",
|
67
|
+
:created_at => arg_that { |arg| arg.kind_of?(Time) }
|
68
|
+
)
|
69
|
+
end
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
73
|
+
describe "comparing custom types with an arg_that matcher" do
|
74
|
+
context "a simple type" do
|
75
|
+
class Dog
|
76
|
+
def bark
|
77
|
+
"wan wan"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
subject { Dog.new }
|
81
|
+
When(:result) { subject.bark }
|
82
|
+
Then { expect(result).to eqish arg_that { |arg| arg.include?("wan") } }
|
83
|
+
And { expect(result).to_not eqish arg_that { |arg| arg.include?("woof") } }
|
84
|
+
end
|
85
|
+
|
86
|
+
context "a type that overrides ==" do
|
87
|
+
class Cat
|
88
|
+
def ==(other)
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
92
|
+
def name
|
93
|
+
"Gorbypuff"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
subject { Cat.new }
|
98
|
+
Then { expect(subject).to eqish arg_that { |arg| arg.name == "Gorbypuff" }}
|
99
|
+
Then { expect(subject).to_not eqish arg_that { |arg| arg.name == "Miles" }}
|
100
|
+
end
|
101
|
+
end
|
64
102
|
end
|
103
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arg-that
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-07-
|
12
|
+
date: 2013-07-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -36,12 +36,14 @@ extensions: []
|
|
36
36
|
extra_rdoc_files: []
|
37
37
|
files:
|
38
38
|
- .gitignore
|
39
|
+
- .travis.yml
|
39
40
|
- Gemfile
|
40
41
|
- LICENSE.txt
|
41
42
|
- README.md
|
42
43
|
- Rakefile
|
43
44
|
- arg_that.gemspec
|
44
45
|
- lib/arg_that.rb
|
46
|
+
- lib/arg_that/eqish.rb
|
45
47
|
- lib/arg_that/that_arg.rb
|
46
48
|
- lib/arg_that/version.rb
|
47
49
|
- spec/arg_that_spec.rb
|