polishing ruby by ryan davis

assert_equal_unordered

Published 2012-03-15 @ 15:00

Tagged minitest

This new minitest assertion is up for debate on github. Will it clean up your tests? Will it clean up your tests enough to warrant addition to minitest, or should it be an add-on gem? tenderlov, drbrain, and I don’t see it cleaning up our tests all that much, but that may be specific to our design patterns.

What do you think?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
diff -r old/lib/minitest/spec.rb new/lib/minitest/spec.rb
--- old/lib/minitest/spec.rb
+++ new/lib/minitest/spec.rb
@@ -312,6 +312,15 @@
   infect_an_assertion :assert_includes, :must_include, :reverse
 
   ##
+  # See MiniTest::Assertions#assert_equal_unordered
+  #
+  #    collection.must_equal_unordered other
+  #
+  # :method: must_equal_unordered
+
+  infect_an_assertion :assert_equal_unordered, :must_equal_unordered, :reverse
+
+  ##
   # See MiniTest::Assertions#assert_instance_of
   #
   #    obj.must_be_instance_of klass
diff -r old/lib/minitest/unit.rb new/lib/minitest/unit.rb
--- old/lib/minitest/unit.rb
+++ new/lib/minitest/unit.rb
@@ -248,6 +248,33 @@
     end
 
     ##
+    # Fails unless +a+ contains the same contents as +b+, regardless
+    # of order.
+    #
+    #    assert_equal_unordered %w[a a b c], %w[a b c a] # pass
+    #
+    # NOTE: This uses Hash#== to determine collection equivalence, as
+    # such, do not expect it to behave the same as +assert_equal+.
+    #
+    #    assert_equal [1], [1.0]                         # pass
+    #    assert_equal({ 1 => true }, { 1.0 => true })    # fail
+    #    assert_equal_unordered [1], [1.0]               # fail
+
+    def assert_equal_unordered a, b, msg = nil
+      msg = message(msg) {
+        "Expected #{mu_pp a} to be equivalent to #{mu_pp b}"
+      }
+
+      assert_kind_of Enumerable, a
+      assert_kind_of Enumerable, b
+
+      c = Hash.new { |h,k| h[k] = 0 }; a.each do |e| c[e] += 1 end
+      d = Hash.new { |h,k| h[k] = 0 }; b.each do |e| d[e] += 1 end
+
+      assert c == d, msg
+    end
+
+    ##
     # Fails unless +obj+ is an instance of +cls+.
 
     def assert_instance_of cls, obj, msg = nil
diff -r old/test/test_minitest_spec.rb new/test/test_minitest_spec.rb
--- old/test/test_minitest_spec.rb
+++ new/test/test_minitest_spec.rb
@@ -65,6 +65,20 @@
     end
   end
 
+  it "needs to be sensible about must_equal_unordered order" do
+    @assertion_count += 6 # must_equal_unordered is 4 assertions
+
+    [1, 2, 3].must_equal_unordered([1, 2, 3]).must_equal true
+
+    assert_triggered "Expected [1, 2] to be equivalent to [1, 2, 3]." do
+      [1, 2].must_equal_unordered [1, 2, 3]
+    end
+
+    assert_triggered "msg.\nExpected [1, 2, 4] to be equivalent to [1, 2, 3]." do
+      [1, 2, 4].must_equal_unordered [1, 2, 3], "msg"
+    end
+  end
+
   it "needs to catch an expected exception" do
     @assertion_count = 2
 
@@ -121,6 +135,7 @@
                         must_be_within_delta
                         must_be_within_epsilon
                         must_equal
+                        must_equal_unordered
                         must_include
                         must_match
                         must_output
@@ -129,7 +144,7 @@
                         must_send
                         must_throw)
 
-    bad = %w[not raise throw send output be_silent]
+    bad = %w[not raise throw send output be_silent equal_unordered]
 
     expected_wonts = expected_musts.map { |m| m.sub(/^must/, 'wont') }
     expected_wonts.reject! { |m| m =~ /wont_#{Regexp.union(*bad)}/ }
diff -r old/test/test_minitest_unit.rb new/test/test_minitest_unit.rb
--- old/test/test_minitest_unit.rb
+++ new/test/test_minitest_unit.rb
@@ -895,6 +895,69 @@
     assert_equal expected, e.message
   end
 
+  def test_assert_equal_unordered_when_comparable_elements
+    @assertion_count = 3
+
+    @tc.assert_equal_unordered [1, 2, 3], [2, 3, 1]
+  end
+
+  def test_assert_equal_unordered_when_not_comparable_elements
+    @assertion_count = 3
+
+    @tc.assert_equal_unordered [true, false, true], [true, true, false]
+  end
+
+  def test_assert_equal_unordered_when_enumerable_actual
+    @assertion_count = 3
+
+    es = Class.new do
+      include Enumerable
+
+      def initialize
+        @elems = [true, false, true]
+      end
+
+      def each
+        @elems.each { |e| yield e }
+      end
+    end.new
+
+    @tc.assert_equal_unordered es, [true, true, false]
+  end
+
+  def test_assert_equal_unordered_triggered_when_actual_has_more_elements_than_expected
+    @assertion_count = 4
+
+    e = @tc.assert_raises MiniTest::Assertion do
+      @tc.assert_equal_unordered [true, true], [true]
+    end
+
+    expected = "Expected [true, true] to be equivalent to [true]."
+    assert_equal expected, e.message
+  end
+
+  def test_assert_equal_unordered_triggered_when_actual_has_less_elements_than_expected
+    @assertion_count = 4
+
+    e = @tc.assert_raises MiniTest::Assertion do
+      @tc.assert_equal_unordered [true], [true, true]
+    end
+
+    expected = "Expected [true] to be equivalent to [true, true]."
+    assert_equal expected, e.message
+  end
+
+  def test_assert_equal_unordered_triggered_when_actual_has_different_elements_than_expected
+    @assertion_count = 4
+
+    e = @tc.assert_raises MiniTest::Assertion do
+      @tc.assert_equal_unordered [true, false, true], [false, false, true]
+    end
+
+    expected = "Expected [true, false, true] to be equivalent to [false, false, true]."
+    assert_equal expected, e.message
+  end
+
   def test_assert_instance_of
     @tc.assert_instance_of String, "blah"
   end
@@ -1272,10 +1335,11 @@
     methods = MiniTest::Assertions.public_instance_methods
     methods.map! { |m| m.to_s } if Symbol === methods.first
 
-    ignores = %w(assert_block assert_no_match assert_not_equal
-                 assert_not_nil assert_not_same assert_nothing_raised
-                 assert_nothing_thrown assert_output assert_raise
-                 assert_raises assert_send assert_silent assert_throws)
+    ignores = %w(assert_block assert_equal_unordered assert_no_match
+                 assert_not_equal assert_not_nil assert_not_same
+                 assert_nothing_raised assert_nothing_thrown
+                 assert_output assert_raise assert_raises
+                 assert_send assert_silent assert_throws)
 
     asserts = methods.grep(/^assert/).sort - ignores
     refutes = methods.grep(/^refute/).sort - ignores