Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
language: php

php:
- 7
- 7.1
- 7.2
- 7.3
- 7.4

cache:
directories:
Expand All @@ -15,4 +16,4 @@ sudo: false
before_install:
- composer update --prefer-source

script: vendor/bin/phpunit tests
script: vendor/bin/phpunit
175 changes: 137 additions & 38 deletions AssertThrows.php
Original file line number Diff line number Diff line change
@@ -1,79 +1,178 @@
<?php
<?php declare(strict_types=1);

namespace Codeception;

use PHPUnit\Framework\Assert;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestCase;
use Throwable;

trait AssertThrows
{
/**
* @param string|null $message
* @param Throwable $exception
*/
private function checkException(?string $message, Throwable $exception)
{
$actualMessage = strtolower($exception->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)
);
}
}
}
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -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
Expand Down
59 changes: 32 additions & 27 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,60 @@
# 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
<?php

class MyTest extends PHPUnit\Framework\TestCase
{
use Codeception\AssertThrows;

//...
}
```

## Usage

Catch exception thrown inside a code block.

```php
<?php

$this->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
<?php
$this->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
<?php
class MyTest extends PHPUnit\Framework\TestCase
{
use Codeception\AssertThrows;

}
$this->assertThrowsWithMessage(
NotFoundException::class, 'my error message', function() {
throw new NotFoundException('my error message');
}
);
```

## License MIT
### License MIT
22 changes: 13 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "[email protected]"
},
{
"name": "Gustavo Nieves",
"homepage": "https://medium.com/@ganieves"
}
]
],
"require": {
"phpunit/phpunit": "^7.5|^8.0|^9.0"
},
"autoload": {
"psr-4": {
"Codeception\\": "./"
}
}
}
7 changes: 7 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<phpunit colors="true">
<testsuites>
<testsuite name="AssertThrows">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
Loading