8
8
use ArrayObject ;
9
9
use InvalidArgumentException ;
10
10
use RuntimeException ;
11
+ use SebastianBergmann \Comparator \ComparisonFailure ;
12
+ use SebastianBergmann \Comparator \Factory ;
11
13
use stdClass ;
12
14
13
15
/**
@@ -28,6 +30,12 @@ class DocumentsMatchConstraint extends Constraint
28
30
private $ sortKeys = false ;
29
31
private $ value ;
30
32
33
+ /** @var ComparisonFailure|null */
34
+ private $ lastFailure ;
35
+
36
+ /** @var Factory */
37
+ private $ comparatorFactory ;
38
+
31
39
/**
32
40
* Creates a new constraint.
33
41
*
@@ -43,26 +51,19 @@ public function __construct($value, $ignoreExtraKeysInRoot = false, $ignoreExtra
43
51
$ this ->ignoreExtraKeysInRoot = $ ignoreExtraKeysInRoot ;
44
52
$ this ->ignoreExtraKeysInEmbedded = $ ignoreExtraKeysInEmbedded ;
45
53
$ this ->placeholders = $ placeholders ;
54
+ $ this ->comparatorFactory = Factory::getInstance ();
46
55
}
47
56
48
- /**
49
- * Returns a string representation of the constraint.
50
- *
51
- * @return string
52
- */
53
- public function toString ()
57
+ protected function additionalFailureDescription ($ other )
54
58
{
55
- return 'matches ' . json_encode ($ this ->value );
59
+ if ($ this ->lastFailure === null ) {
60
+ return '' ;
61
+ }
62
+
63
+ return $ this ->lastFailure ->getMessage ();
56
64
}
57
65
58
- /**
59
- * Evaluates the constraint for parameter $other. Returns true if the
60
- * constraint is met, false otherwise.
61
- *
62
- * @param mixed $other
63
- * @return boolean
64
- */
65
- protected function matches ($ other )
66
+ public function evaluate ($ other , $ description = '' , $ returnResult = false )
66
67
{
67
68
/* TODO: If ignoreExtraKeys and sortKeys are both false, then we may be
68
69
* able to skip preparation, convert both documents to extended JSON,
@@ -73,13 +74,45 @@ protected function matches($other)
73
74
* in all documents and sub-documents. */
74
75
$ other = $ this ->prepareBSON ($ other , true , $ this ->sortKeys );
75
76
77
+ $ success = false ;
78
+ $ this ->lastFailure = null ;
79
+
76
80
try {
77
81
$ this ->assertEquals ($ this ->value , $ other , $ this ->ignoreExtraKeysInRoot );
82
+ $ success = true ;
78
83
} catch (RuntimeException $ e ) {
79
- return false ;
84
+ $ this ->lastFailure = new ComparisonFailure (
85
+ $ this ->value ,
86
+ $ other ,
87
+ $ this ->exporter ->export ($ this ->value ),
88
+ $ this ->exporter ->export ($ other ),
89
+ false ,
90
+ $ e ->getMessage ()
91
+ );
80
92
}
81
93
82
- return true ;
94
+ if ($ returnResult ) {
95
+ return $ success ;
96
+ }
97
+
98
+ if (!$ success ) {
99
+ $ this ->fail ($ other , $ description , $ this ->lastFailure );
100
+ }
101
+ }
102
+
103
+ protected function failureDescription ($ other )
104
+ {
105
+ return 'two BSON objects are equal ' ;
106
+ }
107
+
108
+ /**
109
+ * Returns a string representation of the constraint.
110
+ *
111
+ * @return string
112
+ */
113
+ public function toString ()
114
+ {
115
+ return 'matches ' . $ this ->exporter ->export ($ this ->value );
83
116
}
84
117
85
118
/**
@@ -88,19 +121,24 @@ protected function matches($other)
88
121
* @param ArrayObject $expected
89
122
* @param ArrayObject $actual
90
123
* @param boolean $ignoreExtraKeys
124
+ * @param string $keyPrefix
91
125
* @throws RuntimeException if the documents do not match
92
126
*/
93
- private function assertEquals (ArrayObject $ expected , ArrayObject $ actual , $ ignoreExtraKeys )
127
+ private function assertEquals (ArrayObject $ expected , ArrayObject $ actual , $ ignoreExtraKeys, $ keyPrefix = '' )
94
128
{
95
129
if (get_class ($ expected ) !== get_class ($ actual )) {
96
- throw new RuntimeException (sprintf ('$expected is %s but $actual is %s ' , get_class ($ expected ), get_class ($ actual )));
130
+ throw new RuntimeException (sprintf (
131
+ '%s is not instance of expected class "%s" ' ,
132
+ $ this ->exporter ->shortenedExport ($ actual ),
133
+ get_class ($ expected )
134
+ ));
97
135
}
98
136
99
137
foreach ($ expected as $ key => $ expectedValue ) {
100
138
$ actualHasKey = $ actual ->offsetExists ($ key );
101
139
102
140
if (!$ actualHasKey ) {
103
- throw new RuntimeException ('$actual is missing key: ' . $ key );
141
+ throw new RuntimeException (sprintf ( '$actual is missing key: "%s" ' , $ keyPrefix . $ key) );
104
142
}
105
143
106
144
if (in_array ($ expectedValue , $ this ->placeholders , true )) {
@@ -111,12 +149,53 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, $ignor
111
149
112
150
if (($ expectedValue instanceof BSONArray && $ actualValue instanceof BSONArray) ||
113
151
($ expectedValue instanceof BSONDocument && $ actualValue instanceof BSONDocument)) {
114
- $ this ->assertEquals ($ expectedValue , $ actualValue , $ this ->ignoreExtraKeysInEmbedded );
152
+ $ this ->assertEquals ($ expectedValue , $ actualValue , $ this ->ignoreExtraKeysInEmbedded , $ keyPrefix . $ key . '. ' );
153
+ continue ;
154
+ }
155
+
156
+ if (is_scalar ($ expectedValue ) && is_scalar ($ actualValue )) {
157
+ if ($ expectedValue !== $ actualValue ) {
158
+ throw new ComparisonFailure (
159
+ $ expectedValue ,
160
+ $ actualValue ,
161
+ '' ,
162
+ '' ,
163
+ false ,
164
+ sprintf ('Field path "%s": %s ' , $ keyPrefix . $ key , 'Failed asserting that two values are equal. ' )
165
+ );
166
+ }
167
+
115
168
continue ;
116
169
}
117
170
118
- if (gettype ($ expectedValue ) != gettype ($ actualValue ) || $ expectedValue != $ actualValue ) {
119
- throw new RuntimeException ('$expectedValue != $actualValue for key: ' . $ key );
171
+ // Workaround for ObjectComparator printing the whole actual object
172
+ if (get_class ($ expectedValue ) !== get_class ($ actualValue )) {
173
+ throw new ComparisonFailure (
174
+ $ expectedValue ,
175
+ $ actualValue ,
176
+ '' ,
177
+ '' ,
178
+ false ,
179
+ \sprintf (
180
+ 'Field path "%s": %s is not instance of expected class "%s". ' ,
181
+ $ keyPrefix . $ key ,
182
+ $ this ->exporter ->shortenedExport ($ actualValue ),
183
+ get_class ($ expectedValue )
184
+ )
185
+ );
186
+ }
187
+
188
+ try {
189
+ $ this ->comparatorFactory ->getComparatorFor ($ expectedValue , $ actualValue )->assertEquals ($ expectedValue , $ actualValue );
190
+ } catch (ComparisonFailure $ failure ) {
191
+ throw new ComparisonFailure (
192
+ $ expectedValue ,
193
+ $ actualValue ,
194
+ '' ,
195
+ '' ,
196
+ false ,
197
+ sprintf ('Field path "%s": %s ' , $ keyPrefix . $ key , $ failure ->getMessage ())
198
+ );
120
199
}
121
200
}
122
201
@@ -126,7 +205,7 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, $ignor
126
205
127
206
foreach ($ actual as $ key => $ value ) {
128
207
if (!$ expected ->offsetExists ($ key )) {
129
- throw new RuntimeException ('$actual has extra key: ' . $ key );
208
+ throw new RuntimeException (sprintf ( '$actual has extra key: "%s" ' , $ keyPrefix . $ key) );
130
209
}
131
210
}
132
211
}
0 commit comments