mirror of
https://github.com/pestphp/pest.git
synced 2026-03-07 00:07:22 +01:00
Merge pull request #361 from kbond/throw-expectation
Add `toThrow` expectation
This commit is contained in:
@ -5,13 +5,19 @@ declare(strict_types=1);
|
|||||||
namespace Pest;
|
namespace Pest;
|
||||||
|
|
||||||
use BadMethodCallException;
|
use BadMethodCallException;
|
||||||
|
use Closure;
|
||||||
|
use LogicException;
|
||||||
use Pest\Concerns\Extendable;
|
use Pest\Concerns\Extendable;
|
||||||
use Pest\Concerns\RetrievesValues;
|
use Pest\Concerns\RetrievesValues;
|
||||||
use Pest\Support\Arr;
|
use Pest\Support\Arr;
|
||||||
|
use Pest\Support\NullClosure;
|
||||||
use PHPUnit\Framework\Assert;
|
use PHPUnit\Framework\Assert;
|
||||||
use PHPUnit\Framework\Constraint\Constraint;
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
use ReflectionFunction;
|
||||||
|
use ReflectionNamedType;
|
||||||
use SebastianBergmann\Exporter\Exporter;
|
use SebastianBergmann\Exporter\Exporter;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -749,6 +755,57 @@ final class Expectation
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that executing value throws an exception.
|
||||||
|
*
|
||||||
|
* @param string|Closure $exception string: the exception class
|
||||||
|
* Closure: first parameter = exception class
|
||||||
|
*/
|
||||||
|
public function toThrow($exception, string $exceptionMessage = null): Expectation
|
||||||
|
{
|
||||||
|
$callback = NullClosure::create();
|
||||||
|
|
||||||
|
if ($exception instanceof Closure) {
|
||||||
|
$callback = $exception;
|
||||||
|
$parameters = (new ReflectionFunction($exception))->getParameters();
|
||||||
|
|
||||||
|
if (1 !== count($parameters)) {
|
||||||
|
throw new LogicException('The "toThrow" closure must have a single parameter type-hinted as the class string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($type = $parameters[0]->getType()) instanceof ReflectionNamedType) {
|
||||||
|
throw new LogicException('The "toThrow" closure\'s parameter must be type-hinted as the class string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$exception = $type->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
($this->value)();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if (!class_exists($exception)) {
|
||||||
|
Assert::assertStringContainsString($exception, $e->getMessage());
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exceptionMessage) {
|
||||||
|
Assert::assertStringContainsString($exceptionMessage, $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertInstanceOf($exception, $e);
|
||||||
|
$callback($e);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists($exception)) {
|
||||||
|
throw new ExpectationFailedException("Exception with message \"{$exception}\" not thrown.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ExpectationFailedException("Exception \"{$exception}\" not thrown.");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the given value.
|
* Exports the given value.
|
||||||
*
|
*
|
||||||
|
|||||||
60
tests/Features/Expect/toThrow.php
Normal file
60
tests/Features/Expect/toThrow.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('passes', function () {
|
||||||
|
expect(function () { throw new RuntimeException(); })->toThrow(RuntimeException::class);
|
||||||
|
expect(function () { throw new RuntimeException(); })->toThrow(Exception::class);
|
||||||
|
expect(function () { throw new RuntimeException(); })->toThrow(function (RuntimeException $e) {});
|
||||||
|
expect(function () { throw new RuntimeException('actual message'); })->toThrow(function (Exception $e) {
|
||||||
|
expect($e->getMessage())->toBe('actual message');
|
||||||
|
});
|
||||||
|
expect(function () {})->not->toThrow(Exception::class);
|
||||||
|
expect(function () { throw new RuntimeException('actual message'); })->toThrow('actual message');
|
||||||
|
expect(function () { throw new Exception(); })->not->toThrow(RuntimeException::class);
|
||||||
|
expect(function () { throw new RuntimeException('actual message'); })->toThrow(RuntimeException::class, 'actual message');
|
||||||
|
expect(function () { throw new RuntimeException('actual message'); })->toThrow(function (RuntimeException $e) {}, 'actual message');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures 1', function () {
|
||||||
|
expect(function () {})->toThrow(RuntimeException::class);
|
||||||
|
})->throws(ExpectationFailedException::class, 'Exception "' . RuntimeException::class . '" not thrown.');
|
||||||
|
|
||||||
|
test('failures 2', function () {
|
||||||
|
expect(function () {})->toThrow(function (RuntimeException $e) {});
|
||||||
|
})->throws(ExpectationFailedException::class, 'Exception "' . RuntimeException::class . '" not thrown.');
|
||||||
|
|
||||||
|
test('failures 3', function () {
|
||||||
|
expect(function () { throw new Exception(); })->toThrow(function (RuntimeException $e) {});
|
||||||
|
})->throws(ExpectationFailedException::class, 'Failed asserting that Exception Object');
|
||||||
|
|
||||||
|
test('failures 4', function () {
|
||||||
|
expect(function () { throw new Exception('actual message'); })
|
||||||
|
->toThrow(function (Exception $e) {
|
||||||
|
expect($e->getMessage())->toBe('expected message');
|
||||||
|
});
|
||||||
|
})->throws(ExpectationFailedException::class, 'Failed asserting that two strings are identical');
|
||||||
|
|
||||||
|
test('failures 5', function () {
|
||||||
|
expect(function () { throw new Exception('actual message'); })->toThrow('expected message');
|
||||||
|
})->throws(ExpectationFailedException::class, 'Failed asserting that \'actual message\' contains "expected message".');
|
||||||
|
|
||||||
|
test('failures 6', function () {
|
||||||
|
expect(function () {})->toThrow('actual message');
|
||||||
|
})->throws(ExpectationFailedException::class, 'Exception with message "actual message" not thrown');
|
||||||
|
|
||||||
|
test('failures 7', function () {
|
||||||
|
expect(function () { throw new RuntimeException('actual message'); })->toThrow(RuntimeException::class, 'expected message');
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('not failures', function () {
|
||||||
|
expect(function () { throw new RuntimeException(); })->not->toThrow(RuntimeException::class);
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('closure missing parameter', function () {
|
||||||
|
expect(function () {})->toThrow(function () {});
|
||||||
|
})->throws(LogicException::class, 'The "toThrow" closure must have a single parameter type-hinted as the class string');
|
||||||
|
|
||||||
|
test('closure missing type-hint', function () {
|
||||||
|
expect(function () {})->toThrow(function ($e) {});
|
||||||
|
})->throws(LogicException::class, 'The "toThrow" closure\'s parameter must be type-hinted as the class string');
|
||||||
Reference in New Issue
Block a user