Skip to content

Commit

Permalink
Added assertDoesNotThrow* assertions (and more) (#11)
Browse files Browse the repository at this point in the history
* optimized code, added assertDoesNotThrow assertions, updated tests and phpunit versions.

* Allow to test functions with parameters
  • Loading branch information
Gustavo Nieves authored Oct 2, 2020
1 parent ab1622f commit 2e30869
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 93 deletions.
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

0 comments on commit 2e30869

Please sign in to comment.