From 11fc397d6b79e7353ff4568e118a5576809b7eb5 Mon Sep 17 00:00:00 2001 From: Bernd Stellwag Date: Thu, 18 Oct 2018 19:39:37 +0200 Subject: [PATCH] Asserts: Add expectThrowable() method (#5213) * Asserts: add expectThrowable() With this method you can not only test Exceptions, like with expectException, but also Errors. Signed-off-by: Bernd Stellwag * Asserts tests: set up test class in setUp() Signed-off-by: Bernd Stellwag * Asserts: mention expectThrowable in the documentation Signed-off-by: Bernd Stellwag * Asserts: add type hint Signed-off-by: Bernd Stellwag * Asserts: since we marked expectedException as deprecated, use expectThrowable instead Signed-off-by: Bernd Stellwag * fix typos Signed-off-by: Bernd Stellwag * Asserts: try to make expectThrowable compatible with PHP 5.6 again Signed-off-by: Bernd Stellwag * don't use @expectedException annotation as it will get deprecated see https://github.com/sebastianbergmann/phpunit/issues/3332 --- src/Codeception/Module/Asserts.php | 112 ++++++++++---- tests/unit/Codeception/Module/AssertsTest.php | 141 ++++++++++++------ 2 files changed, 182 insertions(+), 71 deletions(-) diff --git a/src/Codeception/Module/Asserts.php b/src/Codeception/Module/Asserts.php index 6e77930..4687459 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/unit/Codeception/Module/AssertsTest.php b/tests/unit/Codeception/Module/AssertsTest.php index e237175..3c4e20b 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 () { }); } }