diff --git a/docs/modules/Asserts.md b/docs/modules/Asserts.md index 7ae38dc8ab..2d5234d71a 100644 --- a/docs/modules/Asserts.md +++ b/docs/modules/Asserts.md @@ -314,30 +314,39 @@ Checks that condition is positive. ### expectException + +@see expectThrowable +@deprecated Use expectThrowable instead. + + * `param` $exception string or \Exception + * `param` $callback + + +### expectThrowable -Handles and checks exception called inside callback function. -Either exception class name or exception instance should be provided. +Handles and checks throwables (Exceptions/Errors) called inside the callback function. +Either throwable class name or throwable instance should be provided. ```php expectException(MyException::class, function() { +$I->expectThrowable(MyThrowable::class, function() { $this->doSomethingBad(); }); -$I->expectException(new MyException(), function() { +$I->expectThrowable(new MyException(), function() { $this->doSomethingBad(); }); ``` -If you want to check message or exception code, you can pass them with exception instance: +If you want to check message or throwable code, you can pass them with throwable instance: ```php expectException(new MyException("Don't do bad things"), function() { +// will check that throwable MyError is thrown with "Don't do bad things" message +$I->expectThrowable(new MyError("Don't do bad things"), function() { $this->doSomethingBad(); }); ``` - * `param` $exception string or \Exception + * `param` $throwable string or \Throwable * `param` $callback diff --git a/src/Codeception/Module/Asserts.php b/src/Codeception/Module/Asserts.php index 6e77930fdf..46874597d4 100644 --- a/src/Codeception/Module/Asserts.php +++ b/src/Codeception/Module/Asserts.php @@ -455,40 +455,100 @@ public function fail($message) * * @param $exception string or \Exception * @param $callback + * + * @deprecated Use expectThrowable instead */ public function expectException($exception, $callback) { - $code = null; - $msg = null; - if (is_object($exception)) { - /** @var $exception \Exception **/ - $class = get_class($exception); - $msg = $exception->getMessage(); - $code = $exception->getCode(); + $this->expectThrowable($exception, $callback); + } + + /** + * Handles and checks throwables (Exceptions/Errors) called inside the callback function. + * Either throwable class name or throwable instance should be provided. + * + * ```php + * expectThrowable(MyThrowable::class, function() { + * $this->doSomethingBad(); + * }); + * + * $I->expectThrowable(new MyException(), function() { + * $this->doSomethingBad(); + * }); + * ``` + * If you want to check message or throwable code, you can pass them with throwable instance: + * ```php + * expectThrowable(new MyError("Don't do bad things"), function() { + * $this->doSomethingBad(); + * }); + * ``` + * + * @param $throwable string or \Throwable + * @param $callback + */ + public function expectThrowable($throwable, $callback) + { + if (is_object($throwable)) { + /** @var $throwable \Throwable */ + $class = get_class($throwable); + $msg = $throwable->getMessage(); + $code = $throwable->getCode(); } else { - $class = $exception; + $class= $throwable; + $msg = null; + $code = null; } + try { $callback(); - } catch (\Exception $e) { - if (!$e instanceof $class) { - $this->fail(sprintf("Exception of class $class expected to be thrown, but %s caught", get_class($e))); - } - if (null !== $msg and $e->getMessage() !== $msg) { - $this->fail(sprintf( - "Exception of $class expected to be '$msg', but actual message was '%s'", - $e->getMessage() - )); - } - if (null !== $code and $e->getCode() !== $code) { - $this->fail(sprintf( - "Exception of $class expected to have code $code, but actual code was %s", - $e->getCode() - )); - } - $this->assertTrue(true); // increment assertion counter + } catch (\Exception $t) { + $this->checkThrowable($t, $class, $msg, $code); + + return; + } catch (\Throwable $t) { + $this->checkThrowable($t, $class, $msg, $code); + return; } - $this->fail("Expected exception of $class to be thrown, but nothing was caught"); + + $this->fail("Expected throwable of class '$class' to be thrown, but nothing was caught"); + } + + /** + * Check if the given throwable matches the expected data, + * fail (throws an exception) if it does not. + * + * @param \Throwable $throwable + * @param string $expectedClass + * @param string $expectedMsg + * @param int $expectedCode + */ + protected function checkThrowable($throwable, $expectedClass, $expectedMsg, $expectedCode) + { + if (!($throwable instanceof $expectedClass)) { + $this->fail(sprintf( + "Exception of class '$expectedClass' expected to be thrown, but class '%s' was caught", + get_class($throwable) + )); + } + + if (null !== $expectedMsg && $throwable->getMessage() !== $expectedMsg) { + $this->fail(sprintf( + "Exception of class '$expectedClass' expected to have message '$expectedMsg', but actual message was '%s'", + $throwable->getMessage() + )); + } + + if (null !== $expectedCode && $throwable->getCode() !== $expectedCode) { + $this->fail(sprintf( + "Exception of class '$expectedClass' expected to have code '$expectedCode', but actual code was '%s'", + $throwable->getCode() + )); + } + + $this->assertTrue(true); // increment assertion counter } } diff --git a/tests/data/claypit/tests/powers/PowerUpCest.php b/tests/data/claypit/tests/powers/PowerUpCest.php index 13c29eac3e..6c6f96bbb7 100644 --- a/tests/data/claypit/tests/powers/PowerUpCest.php +++ b/tests/data/claypit/tests/powers/PowerUpCest.php @@ -4,7 +4,7 @@ class PowerUpCest { public function iHaveNoPower(PowerGuy $I) { - $I->expectException('Exception', function() use ($I) { + $I->expectThrowable('Exception', function() use ($I) { $I->gotThePower(); }); } @@ -22,4 +22,4 @@ protected function drinkBluePotion(\Codeception\Module\PowerHelper $helper) { $helper->_reconfigure(['has_power' => true]); } -} \ No newline at end of file +} diff --git a/tests/data/snapshots/tests/SnapshotDataCest.php b/tests/data/snapshots/tests/SnapshotDataCest.php index ed99898562..a98ae74058 100644 --- a/tests/data/snapshots/tests/SnapshotDataCest.php +++ b/tests/data/snapshots/tests/SnapshotDataCest.php @@ -18,7 +18,7 @@ public function loadSnapshotAndSkipRefresh(DataTester $I, UserSnapshot $snapshot ]); $snapshot->shouldRefreshSnapshot(false); - $I->expectException(\PHPUnit\Framework\AssertionFailedError::class, function() use ($snapshot) { + $I->expectThrowable(\PHPUnit\Framework\AssertionFailedError::class, function() use ($snapshot) { $snapshot->assert(); }); } diff --git a/tests/data/snapshots/tests/_support/_generated/DataTesterActions.php b/tests/data/snapshots/tests/_support/_generated/DataTesterActions.php index 2bb00d11cd..87ab83f863 100644 --- a/tests/data/snapshots/tests/_support/_generated/DataTesterActions.php +++ b/tests/data/snapshots/tests/_support/_generated/DataTesterActions.php @@ -1,4 +1,4 @@ -expectThrowable(MyThrowable::class, function() { + * $this->doSomethingBad(); + * }); + * + * $I->expectThrowable(new MyException(), function() { + * $this->doSomethingBad(); + * }); + * ``` + * If you want to check message or throwable code, you can pass them with throwable instance: + * ```php + * expectThrowable(new MyError("Don't do bad things"), function() { + * $this->doSomethingBad(); + * }); + * ``` + * + * @param $throwable string of \Throwable + * @param $callback + * @see \Codeception\Module\Asserts::expectThrowable() + */ + public function expectThrowable($throwable, $callback) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('expectThrowable', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * diff --git a/tests/unit/Codeception/Module/AssertsTest.php b/tests/unit/Codeception/Module/AssertsTest.php index e237175049..3c4e20ba9d 100644 --- a/tests/unit/Codeception/Module/AssertsTest.php +++ b/tests/unit/Codeception/Module/AssertsTest.php @@ -1,73 +1,124 @@ module = new \Codeception\Module\Asserts(make_container()); + } + public function testAsserts() { - $module = new \Codeception\Module\Asserts(make_container()); - $module->assertEquals(1, 1); - $module->assertContains(1, [1, 2]); - $module->assertSame(1, 1); - $module->assertNotSame(1, '1'); - $module->assertRegExp('/^[\d]$/', '1'); - $module->assertNotRegExp('/^[a-z]$/', '1'); - $module->assertStringStartsWith('fo', 'foo'); - $module->assertStringStartsNotWith('ba', 'foo'); - $module->assertEmpty([]); - $module->assertNotEmpty([1]); - $module->assertNull(null); - $module->assertNotNull(''); - $module->assertNotNull(false); - $module->assertNotNull(0); - $module->assertTrue(true); - $module->assertNotTrue(false); - $module->assertNotTrue(null); - $module->assertNotTrue('foo'); - $module->assertFalse(false); - $module->assertNotFalse(true); - $module->assertNotFalse(null); - $module->assertNotFalse('foo'); - $module->assertFileExists(__FILE__); - $module->assertFileNotExists(__FILE__ . '.notExist'); - $module->assertInstanceOf('Exception', new Exception()); - $module->assertInternalType('integer', 5); - $module->assertArrayHasKey('one', ['one' => 1, 'two' => 2]); - $module->assertArraySubset(['foo' => [1]], ['foo' => [1, 2]]); - $module->assertCount(3, [1, 2, 3]); + $this->module->assertEquals(1, 1); + $this->module->assertContains(1, [1, 2]); + $this->module->assertSame(1, 1); + $this->module->assertNotSame(1, '1'); + $this->module->assertRegExp('/^[\d]$/', '1'); + $this->module->assertNotRegExp('/^[a-z]$/', '1'); + $this->module->assertStringStartsWith('fo', 'foo'); + $this->module->assertStringStartsNotWith('ba', 'foo'); + $this->module->assertEmpty([]); + $this->module->assertNotEmpty([1]); + $this->module->assertNull(null); + $this->module->assertNotNull(''); + $this->module->assertNotNull(false); + $this->module->assertNotNull(0); + $this->module->assertTrue(true); + $this->module->assertNotTrue(false); + $this->module->assertNotTrue(null); + $this->module->assertNotTrue('foo'); + $this->module->assertFalse(false); + $this->module->assertNotFalse(true); + $this->module->assertNotFalse(null); + $this->module->assertNotFalse('foo'); + $this->module->assertFileExists(__FILE__); + $this->module->assertFileNotExists(__FILE__ . '.notExist'); + $this->module->assertInstanceOf('Exception', new Exception()); + $this->module->assertInternalType('integer', 5); + $this->module->assertArrayHasKey('one', ['one' => 1, 'two' => 2]); + $this->module->assertArraySubset(['foo' => [1]], ['foo' => [1, 2]]); + $this->module->assertCount(3, [1, 2, 3]); } public function testExceptions() { - $module = new \Codeception\Module\Asserts(make_container()); - $module->expectException('Exception', function () { + $this->module->expectException('Exception', function () { throw new Exception; }); - $module->expectException(new Exception('here'), function () { + $this->module->expectException(new Exception('here'), function () { throw new Exception('here'); }); - $module->expectException(new Exception('here', 200), function () { + $this->module->expectException(new Exception('here', 200), function () { throw new Exception('here', 200); }); } - /** - * @expectedException PHPUnit\Framework\AssertionFailedError - */ public function testExceptionFails() { - $module = new \Codeception\Module\Asserts(make_container()); - $module->expectException(new Exception('here', 200), function () { + $this->expectException(PHPUnit\Framework\AssertionFailedError::class); + + $this->module->expectException(new Exception('here', 200), function () { throw new Exception('here', 2); }); } - /** - * @expectedException PHPUnit\Framework\AssertionFailedError - * @expectedExceptionMessageRegExp /RuntimeException/ - */ public function testOutputExceptionTimeWhenNothingCaught() { - $module = new \Codeception\Module\Asserts(make_container()); - $module->expectException(RuntimeException::class, function () { + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageRegExp('/RuntimeException/'); + + $this->module->expectException(RuntimeException::class, function () { + }); + } + + public function testExpectThrowable() + { + $this->module->expectThrowable('Exception', function () { + throw new Exception(); + }); + $this->module->expectThrowable(new Exception('here'), function () { + throw new Exception('here'); + }); + $this->module->expectThrowable(new Exception('here', 200), function () { + throw new Exception('here', 200); + }); + } + + public function testExpectThrowableFailOnDifferentClass() + { + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + + $this->module->expectThrowable(new RuntimeException(), function () { + throw new Exception(); + }); + } + + public function testExpectThrowableFailOnDifferentMessage() + { + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + + $this->module->expectThrowable(new Exception('foo', 200), function () { + throw new Exception('bar', 200); + }); + } + + public function testExpectThrowableFailOnDifferentCode() + { + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + + $this->module->expectThrowable(new Exception('foobar', 200), function () { + throw new Exception('foobar', 2); + }); + } + + public function testExpectThrowableFailOnNothingCaught() + { + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageRegExp('/RuntimeException/'); + + $this->module->expectThrowable(RuntimeException::class, function () { }); } }