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