Merge remote-tracking branch 'origin/master' into add-new-ci-option-to-pest-bin

# Conflicts:
#	tests/.snapshots/success.txt
This commit is contained in:
Fabio Ivona
2021-09-25 09:04:07 +02:00
8 changed files with 520 additions and 14 deletions

View File

@ -157,6 +157,7 @@ final class Expectation
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
$keys = array_keys($value);
$values = array_values($value);
$callbacksCount = count($callbacks);
$index = 0;
@ -165,6 +166,10 @@ final class Expectation
$index = $index < count($values) - 1 ? $index + 1 : 0;
}
if ($callbacksCount > count($values)) {
Assert::assertLessThanOrEqual(count($value), count($callbacks));
}
foreach ($values as $key => $item) {
if (is_callable($callbacks[$key])) {
call_user_func($callbacks[$key], new self($item), new self($keys[$key]));
@ -177,6 +182,88 @@ final class Expectation
return $this;
}
/**
* If the subject matches one of the given "expressions", the expression callback will run.
*
* @template TMatchSubject of array-key
*
* @param callable(): TMatchSubject|TMatchSubject $subject
* @param array<TMatchSubject, (callable(Expectation<TValue>): mixed)|TValue> $expressions
*/
public function match($subject, array $expressions): Expectation
{
$subject = is_callable($subject)
? $subject
: function () use ($subject) {
return $subject;
};
$subject = $subject();
$matched = false;
foreach ($expressions as $key => $callback) {
if ($subject != $key) {
continue;
}
$matched = true;
if (is_callable($callback)) {
$callback(new self($this->value));
continue;
}
$this->and($this->value)->toEqual($callback);
break;
}
if ($matched === false) {
throw new ExpectationFailedException('Unhandled match value.');
}
return $this;
}
/**
* Apply the callback if the given "condition" is falsy.
*
* @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback
*/
public function unless($condition, callable $callback): Expectation
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): mixed {
return $condition;
};
return $this->when(!$condition(), $callback);
}
/**
* Apply the callback if the given "condition" is truthy.
*
* @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback
*/
public function when($condition, callable $callback): Expectation
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): mixed {
return $condition;
};
if ($condition()) {
$callback($this->and($this->value));
}
return $this;
}
/**
* Asserts that two variables have the same type and
* value. Used on objects, it asserts that two

View File

@ -78,6 +78,26 @@ final class TestCall
return $this;
}
/**
* Asserts that the test throws the given `$exceptionClass` when called if the given condition is true.
*
* @param (callable(): bool)|bool $condition
*/
public function throwsIf($condition, string $exception, string $exceptionMessage = null): TestCall
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): mixed {
return $condition;
};
if ($condition()) {
return $this->throws($exception, $exceptionMessage);
}
return $this;
}
/**
* Runs the current test multiple times with
* each item of the given `iterable`.

View File

@ -107,6 +107,11 @@
✓ it catch exceptions
✓ it catch exceptions and messages
✓ it can just define the message
✓ it not catch exceptions if given condition is false
✓ it catch exceptions if given condition is true
✓ it catch exceptions and messages if given condition is true
✓ it can just define the message if given condition is true
✓ it can just define the message if given condition is 1
PASS Tests\Features\Expect\HigherOrder\methods
✓ it can access methods
@ -158,6 +163,17 @@
✓ it properly parses json string
✓ fails with broken json string
PASS Tests\Features\Expect\matchExpectation
✓ it pass
✓ it failures
✓ it runs with truthy
✓ it runs with falsy
✓ it runs with truthy closure condition
✓ it runs with falsy closure condition
✓ it can be passed non-callable values
✓ it fails with unhandled match
✓ it can be used in higher order tests
PASS Tests\Features\Expect\not
✓ not property calls
@ -168,7 +184,7 @@
✓ an exception is thrown if the the type is not iterable
✓ allows for sequences of checks to be run on iterable data
✓ loops back to the start if it runs out of sequence items
it works if the number of items in the iterable is smaller than the number of expectations
fails if the number of iterable items is greater than the number of expectations
✓ it works with associative arrays
✓ it can be passed non-callable values
✓ it can be passed a mixture of value types
@ -481,6 +497,24 @@
✓ closure missing parameter
✓ closure missing type-hint
PASS Tests\Features\Expect\unless
✓ it pass
✓ it failures
✓ it runs with truthy
✓ it skips with falsy
✓ it runs with truthy closure condition
✓ it skips with falsy closure condition
✓ it can be used in higher order tests
PASS Tests\Features\Expect\when
✓ it pass
✓ it failures
✓ it runs with truthy
✓ it skips with falsy
✓ it runs with truthy closure condition
✓ it skips with falsy closure condition
✓ it can be used in higher order tests
PASS Tests\Features\Helpers
✓ it can set/get properties on $this
✓ it throws error if property do not exist
@ -623,10 +657,6 @@
✓ it show the actual dataset of multiple non-named datasets in their description
✓ it show the correct description for mixed named and not-named datasets
PASS Tests\Unit\Plugins\Context
✓ environment is set to CI when --ci option is used
✓ environment is set to Local when --ci option is not used
PASS Tests\Unit\Plugins\Version
✓ it outputs the version when --version is used
✓ it do not outputs version when --version is not used
@ -652,7 +682,6 @@
✓ it alerts users about tests with arguments but no input
✓ it can return an array of all test suite filenames
✓ it can filter the test suite filenames to those with the only method
✓ it does not filter the test suite filenames to those with the only method when working in CI pipeline
PASS Tests\Visual\Help
✓ visual snapshot of help command output
@ -686,5 +715,5 @@
✓ it is a test
✓ it uses correct parent class
Tests: 4 incompleted, 9 skipped, 450 passed
Tests: 4 incompleted, 9 skipped, 475 passed

View File

@ -17,3 +17,23 @@ it('catch exceptions and messages', function () {
it('can just define the message', function () {
throw new Exception('Something bad happened');
})->throws('Something bad happened');
it('not catch exceptions if given condition is false', function () {
$this->assertTrue(true);
})->throwsIf(false, Exception::class);
it('catch exceptions if given condition is true', function () {
throw new Exception('Something bad happened');
})->throwsIf(function () { return true; }, Exception::class);
it('catch exceptions and messages if given condition is true', function () {
throw new Exception('Something bad happened');
})->throwsIf(true, Exception::class, 'Something bad happened');
it('can just define the message if given condition is true', function () {
throw new Exception('Something bad happened');
})->throwsIf(true, 'Something bad happened');
it('can just define the message if given condition is 1', function () {
throw new Exception('Something bad happened');
})->throwsIf(1, 'Something bad happened');

View File

@ -0,0 +1,148 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
$this->matched = null;
});
it('pass', function () {
expect('baz')
->match('foo', [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
'foo' => function ($value) {
$this->matched = 'baz';
return $value->toEqual('baz');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('failures', function () {
expect(true)
->match('foo', [
'bar' => function ($value) {
return $value->toBeTrue();
},
'foo' => function ($value) {
return $value->toBeFalse();
},
]
);
})->throws(ExpectationFailedException::class, 'true is false');
it('runs with truthy', function () {
expect('foo')
->match(1, [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
true => function ($value) {
$this->matched = 'foo';
return $value->toEqual('foo');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('runs with falsy', function () {
expect('foo')
->match(false, [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
false => function ($value) {
$this->matched = 'foo';
return $value->toEqual('foo');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('runs with truthy closure condition', function () {
expect('foo')
->match(
function () { return '1'; }, [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
true => function ($value) {
$this->matched = 'foo';
return $value->toEqual('foo');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('runs with falsy closure condition', function () {
expect('foo')
->match(
function () { return '0'; }, [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
false => function ($value) {
$this->matched = 'foo';
return $value->toEqual('foo');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('can be passed non-callable values', function () {
expect('foo')
->match('pest', [
'bar' => 'foo',
'pest' => 'baz',
]
);
})->throws(ExpectationFailedException::class, 'two strings are equal');
it('fails with unhandled match', function () {
expect('foo')->match('bar', []);
})->throws(ExpectationFailedException::class, 'Unhandled match value.');
it('can be used in higher order tests')
->expect(true)
->match(
function () { return true; }, [
false => function ($value) {
return $value->toBeFalse();
},
true => function ($value) {
return $value->toBeTrue();
},
]
);

View File

@ -1,5 +1,7 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('an exception is thrown if the the type is not iterable', function () {
expect('Foobar')->each->sequence();
})->throws(BadMethodCallException::class, 'Expectation value is not iterable.');
@ -26,16 +28,14 @@ test('loops back to the start if it runs out of sequence items', function () {
expect(static::getCount())->toBe(16);
});
test('it works if the number of items in the iterable is smaller than the number of expectations', function () {
test('fails if the number of iterable items is greater than the number of expectations', function () {
expect([1, 2])
->sequence(
function ($expectation) { $expectation->toBeInt()->toEqual(1); },
function ($expectation) { $expectation->toBeInt()->toEqual(2); },
function ($expectation) { $expectation->toBeInt()->toEqual(3); },
);
expect(static::getCount())->toBe(4);
});
})->throws(ExpectationFailedException::class);
test('it works with associative arrays', function () {
expect(['foo' => 'bar', 'baz' => 'boom'])

View File

@ -0,0 +1,101 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
$this->unlessObject = new stdClass();
$this->unlessObject->trueValue = true;
$this->unlessObject->foo = 'foo';
});
it('pass', function () {
expect('foo')
->unless(
true,
function ($value) {
return $value->toEqual('bar');
}
)
->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('failures', function () {
expect('foo')
->unless(
false,
function ($value) {
return $value->toBeTrue();
}
)
->toEqual('foo');
})->throws(ExpectationFailedException::class, 'is true');
it('runs with truthy', function () {
expect($this->unlessObject)
->unless(
0,
function ($value) {
return $value->trueValue->toBeTrue();
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('skips with falsy', function () {
expect($this->unlessObject)
->unless(
1,
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->unless(
true,
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('runs with truthy closure condition', function () {
expect($this->unlessObject)
->unless(
function () { return '0'; },
function ($value) {
return $value->trueValue->toBeTrue();
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('skips with falsy closure condition', function () {
expect($this->unlessObject)
->unless(
function () { return '1'; },
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('can be used in higher order tests')
->expect(true)
->unless(
function () { return false; },
function ($value) {
return $value->toBeFalse();
}
)
->throws(ExpectationFailedException::class, 'true is false');

View File

@ -0,0 +1,101 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
$this->whenObject = new stdClass();
$this->whenObject->trueValue = true;
$this->whenObject->foo = 'foo';
});
it('pass', function () {
expect('foo')
->when(
true,
function ($value) {
return $value->toEqual('foo');
}
)
->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('failures', function () {
expect('foo')
->when(
true,
function ($value) {
return $value->toBeTrue();
}
)
->toEqual('foo');
})->throws(ExpectationFailedException::class, 'is true');
it('runs with truthy', function () {
expect($this->whenObject)
->when(
1,
function ($value) {
return $value->trueValue->toBeTrue();
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('skips with falsy', function () {
expect($this->whenObject)
->when(
0,
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->when(
false,
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('runs with truthy closure condition', function () {
expect($this->whenObject)
->when(
function () { return '1'; },
function ($value) {
return $value->trueValue->toBeTrue();
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('skips with falsy closure condition', function () {
expect($this->whenObject)
->when(
function () { return '0'; },
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('can be used in higher order tests')
->expect(false)
->when(
function () { return true; },
function ($value) {
return $value->toBeTrue();
}
)
->throws(ExpectationFailedException::class, 'false is true');