diff --git a/.gitignore b/.gitignore index 6f02c50..f584674 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ composer.phar /vendor/ .idea composer.lock +.phpunit.result.cache # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file diff --git a/.travis.yml b/.travis.yml index 9fc157c..5dc9f55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: php php: - - 7 - 7.1 - 7.2 + - 7.3 + - 7.4 cache: directories: @@ -15,4 +16,4 @@ sudo: false before_install: - composer update --prefer-source -script: vendor/bin/phpunit tests \ No newline at end of file +script: vendor/bin/phpunit \ No newline at end of file diff --git a/AssertThrows.php b/AssertThrows.php index e237ced..bffbb57 100644 --- a/AssertThrows.php +++ b/AssertThrows.php @@ -1,79 +1,178 @@ -getMessage()); + + if (!$message || $message === $actualMessage) { + return; + } + + throw new AssertionFailedError( + sprintf( + "Exception message '%s' was expected, but '%s' was received", + $message, + $actualMessage + ) + ); + } /** * Asserts that callback throws an exception * * @param $throws - * @param callable $fn - * @throws \Exception + * @param callable $func + * @param mixed ...$params + * @throws Throwable */ - public function assertThrows($throws, callable $fn) + public function assertThrows($throws, callable $func, ...$params) { - $this->assertThrowsWithMessage($throws, false, $fn); + $this->assertThrowsWithMessage($throws, null, $func, $params); } /** * Asserts that callback throws an exception with a message * - * @param $throws - * @param $message - * @param callable $fn + * @param string|Throwable $throws + * @param string|null $message + * @param callable $func + * @param mixed ...$params + * @throws Throwable */ - public function assertThrowsWithMessage($throws, $message, callable $fn) + public function assertThrowsWithMessage($throws, ?string $message, callable $func, ...$params) { - /** @var $this TestCase * */ - $result = $this->getTestResultObject(); - - if (is_array($throws)) { - $message = ($throws[1]) ? $throws[1] : false; - $throws = $throws[0]; + if ($throws instanceof Throwable) { + $message = $throws->getMessage(); + $throws = get_class($throws); } - if (is_string($message)) { + if ($message) { $message = strtolower($message); } try { - call_user_func($fn); - } catch (AssertionFailedError $e) { + if ($params) { + call_user_func_array($func, $params); + } else { + call_user_func($func); + } + } catch (AssertionFailedError $exception) { - if ($throws !== get_class($e)) { - throw $e; + if ($throws !== get_class($exception)) { + throw $exception; } - if ($message !== false && $message !== strtolower($e->getMessage())) { - throw new AssertionFailedError("Exception message '$message' was expected, but '" . $e->getMessage() . "' was received"); + $this->checkException($message, $exception); + + } catch (Throwable $exception) { + + if (!$throws) { + throw $exception; } - } catch (\Exception $e) { - if ($throws) { - if ($throws !== get_class($e)) { - throw new AssertionFailedError("Exception '$throws' was expected, but " . get_class($e) . " was thrown with message '" . $e->getMessage() . "'"); - } + $actualThrows = get_class($exception); - if ($message !== false && $message !== strtolower($e->getMessage())) { - throw new AssertionFailedError("Exception message '$message' was expected, but '" . $e->getMessage() . "' was received"); - } - } else { - throw $e; + if ($throws !== $actualThrows) { + throw new AssertionFailedError( + sprintf( + "Exception '%s' was expected, but '%s' was thrown with message '%s'", + $throws, + get_class($exception), + $exception->getMessage() + ) + ); } + + $this->checkException($message, $exception); } - if ($throws) { - if (isset($e)) { - $this->assertTrue(true, 'exception handled'); + if (!$throws) { + return; + } + + if (isset($exception)) { + Assert::assertTrue(true, 'Exception handled'); + return; + } + + throw new AssertionFailedError( + sprintf("Exception '%s' was not thrown as expected", $throws) + ); + } + + /** + * Asserts that callback does not throws an exception + * + * @param null|string|Throwable $throws + * @param callable $func + * @param mixed ...$params + */ + public function assertDoesNotThrow($throws, callable $func, ...$params) + { + $this->assertDoesNotThrowWithMessage($throws, null, $func, $params); + } + + /** + * Asserts that callback does not throws an exception with a message + * + * @param null|string|Throwable $throws + * @param string|null $message + * @param callable $func + * @param mixed ...$params + */ + public function assertDoesNotThrowWithMessage($throws, ?string $message, callable $func, ...$params) + { + if ($throws instanceof Throwable) { + $message = $throws->getMessage(); + $throws = get_class($throws); + } + + try { + if ($params) { + call_user_func_array($func, $params); } else { - throw new AssertionFailedError("Exception '$throws' was not thrown as expected"); + call_user_func($func); } - } + } catch (Throwable $exception) { + if (!$throws) { + throw new AssertionFailedError('Exception was not expected to be thrown'); + } + + $actualThrows = get_class($exception); + if ($throws != $actualThrows) { + Assert::assertNotSame($throws, $actualThrows); + return; + } + + if (!$message) { + throw new AssertionFailedError( + sprintf("Exception '%s' was not expected to be thrown", $throws) + ); + } + + $actualMessage = $exception->getMessage(); + + if ($message != $actualMessage) { + Assert::assertNotSame($message, $actualMessage); + return; + } + + throw new AssertionFailedError( + sprintf("Exception '%s' with message '%s' was not expected to be thrown", $throws, $message) + ); + } } } \ No newline at end of file diff --git a/LICENSE b/LICENSE index 701519c..d71ee6d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Codeception Testing Framework +Copyright (c) 2020 Codeception Testing Framework Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Readme.md b/Readme.md index 316273a..9f51c4d 100644 --- a/Readme.md +++ b/Readme.md @@ -1,12 +1,27 @@ # AssertThrows +Handle exceptions inside a test without a stop! Works with PHPUnit and Codeception. + [![Build Status](https://travis-ci.org/Codeception/AssertThrows.svg?branch=master)](https://travis-ci.org/Codeception/AssertThrows) -Handle exceptions inside a test without a stop! +## Installation ---- +``` +composer require codeception/assert-throws --dev +``` -Assertions for exceptions. Works with PHPUnit and Codeception. +Include `AssertThrows` trait it to a TestCase: + +```php +assertThrows(NotFoundException::class, function() { - $this->userController->show(999); + $this->userController->show(99); }); // alternatively -$this->assertThrows(new NotFoundException, function() { - $this->userController->show(999); +$this->assertThrows(new NotFoundException(), function() { + $this->userController->show(99); }); -?> -``` -You can optionally test the exception message: - -```php -assertThrowsWithMessage(NotFoundException::class, 'my error message', function() { - throw new NotFoundException('my error message'); +// you can also assert that an exception is not throw +$this->assertDoesNotThrow(NotFoundException::class, function() { + $this->userController->show(99); }); -?> ``` -### Installation - -```php -composer require codeception/assert-throws --dev -``` - -Include `AssertThrows` trait it to a TestCase: +You can optionally test the exception message: ```php assertThrowsWithMessage( + NotFoundException::class, 'my error message', function() { + throw new NotFoundException('my error message'); + } +); ``` -## License MIT \ No newline at end of file +### License MIT \ No newline at end of file diff --git a/composer.json b/composer.json index 51431bd..f40695e 100644 --- a/composer.json +++ b/composer.json @@ -2,19 +2,23 @@ "name": "codeception/assert-throws", "description": "Assert exception was thrown without stopping a test", "type": "library", - "require": { - "phpunit/phpunit": "^6.4|^7.0|^8.0|^9.0" - }, "license": "MIT", - "autoload": { - "psr-4": { - "Codeception\\": "./" - } - }, "authors": [ { "name": "davert", "email": "davert@codeception.com" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" } - ] + ], + "require": { + "phpunit/phpunit": "^7.5|^8.0|^9.0" + }, + "autoload": { + "psr-4": { + "Codeception\\": "./" + } + } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..f3d532f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/tests/AssertThrowsTest.php b/tests/AssertThrowsTest.php index 8c78d78..f6eea0d 100644 --- a/tests/AssertThrowsTest.php +++ b/tests/AssertThrowsTest.php @@ -1,13 +1,17 @@ -assertThrows(MyException::class, function() { throw new MyException(); }); @@ -15,40 +19,76 @@ public function testBasicException() throw new MyException('with ignored message'); }); $this->assertTrue(true); - $this->assertEquals($count + 3, \PHPUnit\Framework\Assert::getCount()); + $this->assertEquals($count + 3, Assert::getCount()); } public function testExceptionWithMessage() { - $this->assertThrowsWithMessage(MyException::class, "hello", function() { - throw new MyException("hello"); + $this->assertThrowsWithMessage(MyException::class, 'hello', function() { + throw new MyException('hello'); }); } public function testExceptionMessageFails() { try { - $this->assertThrowsWithMessage(MyException::class, "hello", function() { - throw new MyException("hallo"); + $this->assertThrowsWithMessage(MyException::class, 'hello', function() { + throw new MyException('hallo'); }); } catch (AssertionFailedError $e) { - $this->assertEquals("Exception message 'hello' was expected, but 'hallo' was received", $e->getMessage()); + $this->assertEquals( + "Exception message 'hello' was expected, but 'hallo' was received", + $e->getMessage() + ); return; } - $this->fail("Ups :("); + $this->fail('Ups :('); } public function testExceptionMessageCaseInsensitive() { - $this->assertThrowsWithMessage(MyException::class, "Message and Expected Message CAN have different case", function() { - throw new MyException("Message and expected message can have different case"); - }); + $this->assertThrowsWithMessage( + MyException::class, + 'Message and Expected Message CAN have different case', + function() { + throw new MyException('Message and expected message can have different case'); + } + ); } -} + public function testAssertThrowsWithParams() + { + $func = function (string $foo, string $bar): void { + throw new Exception($foo.$bar); + }; + + $this->assertThrowsWithMessage( + Exception::class, + 'foobar', + $func, + 'foo', + 'bar' + ); + } + + public function testAssertDoesNotThrow(): void + { + $func = function (): void { + throw new Exception('foo'); + }; -class MyException extends Exception { + $this->assertDoesNotThrow(RuntimeException::class, $func); + $this->assertDoesNotThrowWithMessage(RuntimeException::class, 'bar', $func); + $this->assertDoesNotThrowWithMessage(RuntimeException::class, 'foo', $func); + $this->assertDoesNotThrow(new RuntimeException(), $func); + $this->assertDoesNotThrow(new RuntimeException('bar'), $func); + $this->assertDoesNotThrow(new RuntimeException('foo'), $func); + $this->assertDoesNotThrowWithMessage(Exception::class, 'bar', $func); + $this->assertDoesNotThrow(new Exception('bar'), $func); + } +} +final class MyException extends Exception { } \ No newline at end of file