Skip to content

Commit

Permalink
Add messages to AssertionFailedError exceptions.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zrnik committed Sep 15, 2021
1 parent 29b5432 commit fe00749
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 91 deletions.
8 changes: 6 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "zrnik/phpunit-exceptions",
"description": "Trait for easier exception testing in PHPUnit.",
"version": "v0.0.1",
"license": "MIT",
"authors": [
{
Expand All @@ -24,10 +23,15 @@
"phpunit/phpunit": "^9"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
"phpstan/phpstan": "^0.12"
},
"scripts": {
"phpunit": "phpunit tests",
"phpstan": "phpstan analyse src tests -l max"
"phpstan": "phpstan analyse src tests -l max",
"tests": [
"@phpstan",
"@phpunit"
]
}
}
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class ExampleTest extends TestCase
$this->expectException(NotInRangeException::class);
$exampleObject->assertRange(0);
//The execution ends here, the method will not continue,
// after first exception thrown so i need to create
// after first exception thrown, so I need to create
// method for every exception tested...
}

Expand Down Expand Up @@ -118,7 +118,7 @@ class ExampleTest extends TestCase

try {
$exampleObject->assertRange(0);
// I don't want to write so long error text everytime i am checking for exceptions!
// I don't want to write so long error text everytime I am checking for exceptions!
throw new AssertionFailedError(sprintf("Exception '%s' expected, but not thrown!", NotInRangeException::class));
} catch (NotInRangeException $ex) {
$this->addToAssertionCount(1); // Yey! Thrown!
Expand Down
83 changes: 16 additions & 67 deletions src/AssertException.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,25 @@ public static function assertExceptionThrown(
string $expectedType, Closure $closure
): void
{
$exceptionType = self::getThrownExceptionType($closure);
$exceptionResults = self::getThrownExceptionResult($closure);

if ($exceptionType === null)
if ($exceptionResults === null) {
throw new AssertionFailedError(
sprintf(
"Exception '%s' expected, none thrown!",
$expectedType
)
);
}

if ($exceptionType !== $expectedType)
if ($exceptionResults->type !== $expectedType) {
throw new AssertionFailedError(
sprintf(
"Exception '%s' expected, '%s' thrown!",
$expectedType, $exceptionType
"Exception '%s' expected, '%s' thrown!\nMessage: %s",
$expectedType, $exceptionResults->type, $exceptionResults->message
)
);
}
}

/**
Expand All @@ -44,86 +46,33 @@ public static function assertNoExceptionThrown(
Closure $closure
): void
{
$exceptionType = self::getThrownExceptionType($closure);
$exceptionResult = self::getThrownExceptionResult($closure);

if ($exceptionType !== null)
if ($exceptionResult !== null) {
throw new AssertionFailedError(
sprintf(
"No exception expected, but '%s' thrown!",
$exceptionType
"No exception expected, but '%s' was thrown!\nMessage: %s",
$exceptionResult->type, $exceptionResult->message
)
);
}
}

// TODO: assertExceptionCode, assertExceptionMessage

//region Exception Info Mining

/**
* @param Closure $closure
* @return string|null
*/
private static function getThrownExceptionType(Closure $closure): ?string
{
$exception = self::getThrownException($closure);

if ($exception === null)
return null;

/**
* @var string|false $class
*/
$class = @get_class($exception);

if ($class === false)
return null;

return $class;
}

/**
* @param Closure $closure
* @return string|null
* @return ThrownResult|null
*/
private static function getThrownExceptionMessage(Closure $closure): ?string
{
$exception = self::getThrownException($closure);

if ($exception === null)
return null;

return $exception->getMessage();
}

/**
* @param Closure $closure
* @return int|null
*/
private static function getThrownExceptionCode(Closure $closure): ?int
{
$exception = self::getThrownException($closure);

if ($exception === null)
return null;

return $exception->getCode();
}

/**
* @param Closure $closure
* @return Throwable|null
*/
private static function getThrownException(Closure $closure): ?Throwable
private static function getThrownExceptionResult(Closure $closure): ?ThrownResult
{
try {
$closure();
} catch (Throwable $t) {
return $t;
return ThrownResult::createFromThrowable($t);
}

return null;
}

//endregion

}
}
2 changes: 1 addition & 1 deletion src/Exceptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
trait Exceptions
{
// PHPUnit Method
abstract function addToAssertionCount(int $howMuch);
abstract protected function addToAssertionCount(int $howMuch);

/**
* This method will run the closure and
Expand Down
45 changes: 45 additions & 0 deletions src/ThrownResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types=1);

namespace Zrnik\PHPUnit;

use Throwable;

class ThrownResult
{
public string $type;
public string $message;
public int $code;

final private function __construct(
string $type, string $message, int $code
)
{
$this->type = $type;
$this->message = $message;
$this->code = $code;
}

public static function createFromThrowable(Throwable $throwable): ThrownResult
{
return new static(
static::getExceptionType($throwable),
$throwable->getMessage(),
$throwable->getCode()
);
}

/**
* @param Throwable $throwable
* @return string
*/
private static function getExceptionType(Throwable $throwable): string
{
/**
* As the type-check makes sure we only get 'Throwable' type,
* we will never get 'false' from 'get_class' function.
*
* get_debug_type is better, but php 7.4 still supported.
*/
return @get_class($throwable);
}
}
2 changes: 1 addition & 1 deletion tests/AssertExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function test_ExpectException_First(): void
$this->expectException(NotInRangeException::class);
$exampleObject->assertRange(0);
//The execution ends here, the method will not continue,
// after first exception thrown so i need to create
// after first exception thrown, so I need to create
// method for every exception tested...
}

Expand Down
24 changes: 6 additions & 18 deletions tests/ExampleObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,30 @@
class ExampleObject
{

const Min = 1;
const Max = 10;
public const Min = 1;
public const Max = 10;

/**
* @param int $number
*/
public function assertRange(int $number): void
{
if($number < static::Min)
if($number < static::Min) {
throw new NotInRangeException(
sprintf(
"Number '%s' is lower than '%s'",
$number, self::Min
)
);
}

if($number > static::Max)
if($number > static::Max) {
throw new NotInRangeException(
sprintf(
"Number '%s' is larger than '%s'",
$number, self::Max
)
);
}
}

/**
* @param int $number
*/
public function assertRangeAlwaysError(int $number): void
{
throw new NotInRangeException(
sprintf(
"Number '%s' is bad!",
$number
)
);
}

}

0 comments on commit fe00749

Please sign in to comment.