Skip to content

Commit cb51adc

Browse files
[9.x] Add closure based exceptions testing (#42155)
* Add closure based exceptions testing * formatting Co-authored-by: Taylor Otwell <[email protected]>
1 parent 598a8c8 commit cb51adc

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace Illuminate\Foundation\Testing\Concerns;
44

5+
use Closure;
56
use Illuminate\Contracts\Debug\ExceptionHandler;
7+
use Illuminate\Testing\Assert;
68
use Illuminate\Validation\ValidationException;
79
use Symfony\Component\Console\Application as ConsoleApplication;
810
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -64,8 +66,7 @@ protected function withoutExceptionHandling(array $except = [])
6466
$this->originalExceptionHandler = app(ExceptionHandler::class);
6567
}
6668

67-
$this->app->instance(ExceptionHandler::class, new class($this->originalExceptionHandler, $except) implements ExceptionHandler
68-
{
69+
$this->app->instance(ExceptionHandler::class, new class($this->originalExceptionHandler, $except) implements ExceptionHandler {
6970
protected $except;
7071
protected $originalHandler;
7172

@@ -147,4 +148,50 @@ public function renderForConsole($output, Throwable $e)
147148

148149
return $this;
149150
}
151+
152+
/**
153+
* Assert that the given callback throws an exception with the given message when invoked.
154+
*
155+
* @param \Closure $test
156+
* @param class-string<\Throwable> $expectedClass
157+
* @param string|null $expectedMessage
158+
* @return $this
159+
*/
160+
protected function assertThrows(Closure $test, string $expectedClass = Throwable::class, ?string $expectedMessage = null)
161+
{
162+
try {
163+
$test();
164+
165+
$thrown = false;
166+
} catch (Throwable $exception) {
167+
$thrown = $exception instanceof $expectedClass;
168+
169+
$actualMessage = $exception->getMessage();
170+
}
171+
172+
if (! $thrown) {
173+
Assert::fail(
174+
sprintf(
175+
'Failed asserting that exception of type "%s" is thrown.',
176+
$expectedClass
177+
)
178+
);
179+
}
180+
181+
if (isset($expectedMessage)) {
182+
if (! isset($actualMessage)) {
183+
Assert::fail(
184+
sprintf(
185+
'Failed asserting that exception of type "%s" with message "%s" is thrown.',
186+
$expectedClass,
187+
$expectedMessage
188+
)
189+
);
190+
} else {
191+
Assert::assertStringContainsString($expectedMessage, $actualMessage);
192+
}
193+
}
194+
195+
return $this;
196+
}
150197
}

tests/Foundation/FoundationExceptionsHandlerTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@
1010
use Illuminate\Contracts\View\Factory as ViewFactory;
1111
use Illuminate\Database\RecordsNotFoundException;
1212
use Illuminate\Foundation\Exceptions\Handler;
13+
use Illuminate\Foundation\Testing\Concerns\InteractsWithExceptionHandling;
1314
use Illuminate\Http\RedirectResponse;
1415
use Illuminate\Http\Request;
1516
use Illuminate\Routing\Redirector;
1617
use Illuminate\Routing\ResponseFactory;
1718
use Illuminate\Support\MessageBag;
19+
use Illuminate\Testing\Assert;
1820
use Illuminate\Validation\ValidationException;
1921
use Illuminate\Validation\Validator;
2022
use InvalidArgumentException;
2123
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
2224
use Mockery as m;
2325
use OutOfRangeException;
26+
use PHPUnit\Framework\AssertionFailedError;
2427
use PHPUnit\Framework\TestCase;
2528
use Psr\Log\LoggerInterface;
2629
use Psr\Log\LogLevel;
@@ -34,6 +37,7 @@
3437
class FoundationExceptionsHandlerTest extends TestCase
3538
{
3639
use MockeryPHPUnitIntegration;
40+
use InteractsWithExceptionHandling;
3741

3842
protected $config;
3943

@@ -367,6 +371,54 @@ public function getErrorView($e)
367371

368372
$this->assertNull($handler->getErrorView(new HttpException(404)));
369373
}
374+
375+
public function testAssertExceptionIsThrown()
376+
{
377+
$this->assertThrows(function () {
378+
throw new Exception;
379+
});
380+
$this->assertThrows(function () {
381+
throw new CustomException;
382+
});
383+
$this->assertThrows(function () {
384+
throw new CustomException;
385+
}, CustomException::class);
386+
$this->assertThrows(function () {
387+
throw new Exception('Some message.');
388+
}, expectedMessage: 'Some message.');
389+
$this->assertThrows(function () {
390+
throw new CustomException('Some message.');
391+
}, expectedMessage: 'Some message.');
392+
$this->assertThrows(function () {
393+
throw new CustomException('Some message.');
394+
}, expectedClass: CustomException::class, expectedMessage: 'Some message.');
395+
396+
try {
397+
$this->assertThrows(function () {
398+
throw new Exception;
399+
}, CustomException::class);
400+
$testFailed = true;
401+
} catch (AssertionFailedError $exception) {
402+
$testFailed = false;
403+
}
404+
405+
if ($testFailed) {
406+
Assert::fail('assertThrows failed: non matching exceptions are thrown.');
407+
}
408+
409+
try {
410+
$this->assertThrows(function () {
411+
throw new Exception('Some message.');
412+
}, expectedClass: Exception::class, expectedMessage: 'Other message.');
413+
$testFailed = true;
414+
} catch (AssertionFailedError $exception) {
415+
$testFailed = false;
416+
}
417+
418+
if ($testFailed) {
419+
Assert::fail('assertThrows failed: non matching message are thrown.');
420+
}
421+
}
370422
}
371423

372424
class CustomException extends Exception

0 commit comments

Comments
 (0)