-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAssert.php
More file actions
413 lines (386 loc) · 14.4 KB
/
Assert.php
File metadata and controls
413 lines (386 loc) · 14.4 KB
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
<?php
declare(strict_types=1);
namespace Testo;
use Testo\Assert\Api\Builtin\ArrayType;
use Testo\Assert\Api\Builtin\FloatType;
use Testo\Assert\Api\Builtin\IntType;
use Testo\Assert\Api\Builtin\IterableType;
use Testo\Assert\Api\Builtin\NumericType;
use Testo\Assert\Api\Builtin\ObjectType;
use Testo\Assert\Api\Builtin\StringType;
use Testo\Assert\Api\Json\JsonAbstract;
use Testo\Assert\Internal\Assertion\AssertArray;
use Testo\Assert\Internal\Assertion\AssertFloat;
use Testo\Assert\Internal\Assertion\AssertInt;
use Testo\Assert\Internal\Assertion\AssertIterable;
use Testo\Assert\Internal\Assertion\AssertJson;
use Testo\Assert\Internal\Assertion\AssertObject;
use Testo\Assert\Internal\Assertion\AssertString;
use Testo\Assert\Internal\StaticState;
use Testo\Assert\Internal\Support;
use Testo\Assert\State\Assertion\AssertionException;
use Testo\Assert\State\Assertion\ComparisonFailure;
use Testo\Assert\State\Test\Fail;
use Testo\Common\Attribute\AssertMethod;
/**
* Assertion utilities.
*
* @api
*/
final class Assert
{
/**
* Asserts that two values are the same (identical).
*
* @param mixed $actual The actual value to compare against the expected value.
* @param mixed $expected The expected value.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function same(mixed $actual, mixed $expected, string $message = ''): void
{
$actual === $expected
? StaticState::success($actual, 'is the same', $message)
: StaticState::fail(new ComparisonFailure(
expected: $expected,
actual: $actual,
value: Support::stringify($actual),
assertion: 'is the same as `' . Support::stringify($expected) . '`',
context: $message,
reason: 'expected `' . Support::stringify($expected) . '`, got `' . Support::stringify($actual) . '`',
));
}
/**
* Asserts that two values are the not same (not identical).
*
* @param mixed $actual The actual value to compare against the expected value.
* @param mixed $expected The expected value.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function notSame(mixed $actual, mixed $expected, string $message = ''): void
{
$actual !== $expected
? StaticState::success($actual, 'is not same as `' . Support::stringify($expected) . '`', $message)
: StaticState::fail(new ComparisonFailure(
expected: $expected,
actual: $actual,
value: Support::stringify($actual),
assertion: 'is not the same as `' . Support::stringify($expected) . '`',
context: $message,
reason: 'both values are identical',
));
}
/**
* Asserts that two values are equal (not strict).
*
* @param mixed $actual The actual value to compare against the expected value.
* @param mixed $expected The expected value.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function equals(mixed $actual, mixed $expected, string $message = ''): void
{
$actual == $expected
? StaticState::success($actual, 'equals to `' . Support::stringify($expected) . '`', $message)
: StaticState::fail(new ComparisonFailure(
expected: $expected,
actual: $actual,
value: Support::stringify($actual),
assertion: 'equals to `' . Support::stringify($expected) . '`',
context: $message,
reason: 'expected `' . Support::stringify($expected) . '`, got `' . Support::stringify($actual) . '`',
));
}
/**
* Asserts that two values are not equal (not strict).
*
* @param mixed $actual The actual value to compare against the expected value.
* @param mixed $expected The expected value.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function notEquals(mixed $actual, mixed $expected, string $message = ''): void
{
$actual != $expected
? StaticState::success($actual, 'is not equals to `' . Support::stringify($expected) . '`', $message)
: StaticState::fail(new ComparisonFailure(
expected: $expected,
actual: $actual,
value: Support::stringify($actual),
assertion: 'is not equals to `' . Support::stringify($expected) . '`',
context: $message,
reason: 'both values are equal',
));
}
/**
* Asserts that the value is true.
*
* @param mixed $actual The actual value to check.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function true(mixed $actual, string $message = ''): void
{
$actual === true
? StaticState::success($actual, 'is exactly `true`', $message)
: StaticState::fail(new ComparisonFailure(
expected: true,
actual: $actual,
value: Support::stringify($actual),
assertion: 'is exactly `true`',
context: $message,
reason: 'expected `true`, got `' . Support::stringify($actual) . '`',
));
}
/**
* Asserts that the value is false.
*
* @param mixed $actual The actual value to check.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function false(mixed $actual, string $message = ''): void
{
$actual === false
? StaticState::success($actual, 'is exactly `false`', $message)
: StaticState::fail(new ComparisonFailure(
expected: false,
actual: $actual,
value: Support::stringify($actual),
assertion: 'is exactly `false`',
context: $message,
reason: 'expected `false`, got `' . Support::stringify($actual) . '`',
));
}
/**
* Asserts that the actual object is an instance of the expected class/interface.
*
* @template ExpectedType
*
* @param mixed $actual The actual object to check.
* @param class-string<ExpectedType> $expected Fully-qualified class or interface name.
* @param string $message Optional message for the assertion.
* @throws AssertionException when the assertion fails.
*
* @psalm-assert ExpectedType $actual
* @phpstan-assert ExpectedType $actual
*/
public static function instanceOf(mixed $actual, string $expected, string $message = ''): ObjectType
{
return AssertObject::validateAndCreate($actual)->instanceOf($expected, $message);
}
/**
* Asserts that given collection contains expected value.
*
* @param iterable $haystack Iterable (array or Traversable) to search in.
* @param mixed $needle The expected value.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
public static function contains(iterable $haystack, mixed $needle, string $message = ''): void
{
foreach ($haystack as $element) {
if ($needle === $element) {
StaticState::success($haystack, 'contains `' . Support::stringify($needle) . '`', $message);
return;
}
}
StaticState::fail(new ComparisonFailure(
expected: $needle,
actual: $haystack,
value: Support::stringify($haystack),
assertion: 'contains `' . Support::stringify($needle) . '`',
context: $message,
reason: '`' . Support::stringify($needle) . '` not found',
));
}
/**
* Asserts that the given value is null.
*
* @param mixed $actual The actual value to check for null.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function null(
mixed $actual,
string $message = '',
): void {
$actual === null
? StaticState::success($actual, 'is exactly `null`', $message)
: StaticState::fail(new ComparisonFailure(
expected: null,
actual: $actual,
value: Support::stringify($actual),
assertion: 'is exactly `null`',
context: $message,
reason: 'expected `null`, got `' . Support::stringify($actual) . '`',
));
}
/**
* Checks if a value is blank.
*
* Successful only for values representing absence of data:
* null, empty string, empty array or countable object with no elements.
*
* Unlike empty(), does not consider false, 0, and "0" as blank,
* since they represent valid data.
* @param mixed $actual The actual value to check for blank.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function blank(
mixed $actual,
string $message = '',
): void {
if (
$actual === null || $actual === '' || $actual === [] || ($actual instanceof \Countable && \count($actual) === 0)
) {
StaticState::success($actual, 'is blank', $message);
return;
}
StaticState::fail(new AssertionException(
value: Support::stringify($actual),
assertion: 'is blank',
context: $message,
reason: 'value contains data',
details: '',
));
}
/**
* Asserts that the given value has the expected count.
*
* @param \Countable|iterable $actual The actual value to check for count.
* @param int $expected The expected count.
* @param string $message Short description about what exactly is being asserted.
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function count(\Countable|iterable $actual, int $expected, string $message = ''): void
{
if ($actual instanceof \Countable) {
$count = \count($actual);
if ($count === $expected) {
StaticState::success($actual, "A \Countable has count `$expected`", $message);
return;
}
StaticState::fail(new ComparisonFailure(
expected: $expected,
actual: $actual,
value: Support::stringify($actual),
assertion: "has count `$expected`",
context: $message,
reason: "expected `$expected`, got `$count`",
));
}
AssertIterable::validateAndCreate($actual)->hasCount($expected);
}
/**
* Asserts that the given value is of `string` data type.
*
* @throws AssertionException when the assertion fails.
*/
#[AssertMethod]
public static function string(mixed $actual): StringType
{
return AssertString::validateAndCreate($actual);
}
/**
* Asserts that the given value is of `int` data type.
*
* @throws AssertionException
*/
public static function int(mixed $actual): IntType
{
return AssertInt::validateAndCreate($actual);
}
/**
* Asserts that the given value is of `float` data type.
*
* @throws AssertionException
*/
public static function float(mixed $actual): FloatType
{
return AssertFloat::validateAndCreate($actual);
}
/**
* Asserts that the given value is of `numeric` data type.
*
* Numeric type includes integer, float, and numeric strings.
*
* @throws AssertionException
*
* @deprecated To be implemented
*/
public static function numeric(mixed $actual): NumericType
{
throw new \LogicException('Not implemented yet');
}
/**
* Asserts that the given string is a valid JSON.
*
* @param string $actual The actual JSON string to check.
* @throws AssertionException when the value is not valid JSON.
*/
public static function json(string $actual): JsonAbstract
{
return AssertJson::validateAndCreate($actual);
}
/**
* Asserts that the given value is of `iterable` data type.
*
* Iterables include arrays and objects implementing iterable interface.
* Does not work with Generators.
*
* @throws AssertionException
*/
public static function iterable(mixed $actual): IterableType
{
return AssertIterable::validateAndCreate($actual);
}
/**
* Asserts that the given value is of `array` data type.
*
* @throws AssertionException
*/
public static function array(mixed $actual): ArrayType
{
return AssertArray::validateAndCreate($actual);
}
/**
* Asserts that the given value is of `object` data type.
*
* @throws AssertionException
*
*/
public static function object(mixed $actual): ObjectType
{
return AssertObject::validateAndCreate($actual);
}
/**
* Fails the test.
*
* Use this method to explicitly indicate that the test should end in failure.
*
* If this method is called, it is expected that the test will end with an exception thrown by this method.
* If the test catches this exception and continues execution, it will be marked as Risky.
*
* @param string $message The reason for the failure.
* @throws Fail
*/
#[AssertMethod]
public static function fail(string $message = ''): never
{
$exception = new Fail($message);
StaticState::expectFail($exception);
StaticState::fail($exception);
}
}