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