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.
@@ -0,0 +1,2 @@
1
+ language: ruby
2
+ script: 'bundle exec rspec'
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 `created_time`, nor do we know how to specify it exactly. That means we can't just do this:
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.should == {:name => "Bob", :age => 28, :email => "bob@example.com" :created_at => 2013-07-18 21:40:58 -0400}
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].should == "Bob"
36
- result[:age].should == 28
37
- result[:email].should == "bob@example.com"
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.should == {
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 the intentionality about how specific any given equality assertion needs to be. The status quo seems to be to either "always specify everything exactly, but if that gets hard, specify the remainder arbitrarily." And that's not terrific.
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.should == {
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
- ## known issues
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
- `arg_that` does you no good on symbols, as the equality check short-circuits the call to `==` on the receiver.
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
- Any ideas on how to make this pass?
99
+ That is to say, if:
94
100
 
95
101
  ``` ruby
96
- :foo.should == arg_that { true }
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
+
@@ -5,4 +5,10 @@ module ArgThat
5
5
  def arg_that(&blk)
6
6
  ThatArg.new(&blk)
7
7
  end
8
+
9
+ def self.included(includer)
10
+ if defined?(RSpec) && includer.ancestors.include?(RSpec::Core::ExampleGroup)
11
+ require "arg_that/eqish"
12
+ end
13
+ end
8
14
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ArgThat
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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
- context "catch-all" do
13
- Then { :foo.should == arg_that { true }}
14
+ context "arg_that { true } always matches" do
15
+ Then { expect(subject).to eqish arg_that { true } }
16
+ end
14
17
 
15
- Then { 5.should == arg_that { true } }
16
- Then { 5.should_not == arg_that { false } }
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
- context "direct value equality" do
20
- Then { 1.should == arg_that {|arg| arg.kind_of?(Fixnum) } }
21
- Then { 1.should_not == arg_that {|arg| arg.kind_of?(String) } }
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
- context "a value inside something else" do
25
- Then { [5, 6, 1].should == [5, 6, arg_that {|arg| arg.kind_of?(Fixnum) }]}
26
- Then { [5, 6, 1].should_not == [5, 6, arg_that {|arg| arg > 1 }]}
27
- Then do
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
- Then do
38
- {
39
- :zip_code => 48176,
40
- :owner => "Fred Jim",
41
- :last_audit => Time.new(2012, 8, 12)
42
- }.should == {
43
- :zip_code => 48176,
44
- :owner => "Fred Jim",
45
- :last_audit => arg_that { |arg| arg > Time.new(2012, 1, 1) }
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
- Then do
50
- {
51
- :name => "Bob",
52
- :age => 28,
53
- :email => "bob@example.com",
54
- :created_at => Time.new(2013, 7, 18, 21, 40, 58)
55
- }.should == {
56
- :name => "Bob",
57
- :age => 28,
58
- :email => "bob@example.com",
59
- :created_at => arg_that { |arg| arg.kind_of?(Time) }
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.1
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-19 00:00:00.000000000 Z
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