diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ffd6d..2b8568d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +## 0.4.0 - 2020-03-09 +### Changed: + - TestCase is now abstract +### Added: + - assertPromiseFulfillsWithInstanceOf() to check class of the resolution value + ## 0.3.0 - 2020-03-08 ### Updated: - Dependencies diff --git a/README.md b/README.md index 124d362..6d4068a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ assertions for testing ReactPHP promises. - [Assertions](#assertions) - [assertPromiseFulfills()](#assertpromisefulfills) - [assertPromiseFulfillsWith()](#assertpromisefulfillswith) + - [assertPromiseFulfillsWithInstanceOf()](#assertpromisefulfillswithinstanceof) - [assertPromiseRejects()](#assertpromiserejects()) - [assertPromiseRejectsWith()](#assertpromiserejectswith) @@ -33,35 +34,36 @@ The recommended way to install this library is via [Composer](https://getcompose See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. ``` -composer require seregazhuk/react-promise-testing +composer require seregazhuk/react-promise-testing --dev ``` ## Quick Start -To start using it extend your test classes from `seregazhuk\React\PromiseTesting\TestCase` class, +Just extend your test classes from `seregazhuk\React\PromiseTesting\TestCase` class, which itself extends PHPUnit `TestCase`: ```php -class MyTest extends TestCase +final class MyTest extends TestCase { /** @test */ - public function promise_fulfills() + public function promise_fulfills_with_a_response_object() { - $resolve = function(callable $resolve, callable $reject) { - return $resolve('Promise resolved!'); - }; - - $cancel = function(callable $resolve, callable $reject) { - $reject(new \Exception('Promise cancelled!')); - }; - - $promise = new Promise($resolve, $cancel); - $this->assertPromiseFulfills($promise); + $browser = new Clue\React\Buzz\Browser($this->eventLoop()); + $promise = $browser->get('http://www.google.com/'); + $this->assertPromiseFulfillsWithInstanceOf($promise, ResponseInterface::class); } } ``` -Test above checks that a specified promise fulfills. If the promise rejects this test fails. +Test above checks that a specified promise fulfills with an instance of `ResponseInterface`. + +## Event loop + +To make promise assertions we need to run the loop. Before each test a new instance of the event loop +is being created (inside `setUp()` method). If you need the loop to build your dependencies you **should** +use `eventLoop()` method to retrieve it. + + ## Assertions @@ -101,6 +103,7 @@ Failed asserting that promise fulfills. Promise was rejected. ``` ### assertPromiseFulfillsWith() + `assertPromiseFulfillsWith(PromiseInterface $promise, $value, int $timeout = null): void` The test fails if the `$promise` doesn't fulfills with a specified `$value`. @@ -136,6 +139,42 @@ Failed asserting that promise fulfills with a specified value. Failed asserting that 1234 matches expected 1. ``` +### assertPromiseFulfillsWithInstanceOf() + +`assertPromiseFulfillsWithInstanceOf(PromiseInterface $promise, string $class, int $timeout = null): void` + +The test fails if the `$promise` doesn't fulfills with an instance of specified `$class`. + +You can specify `$timeout` in seconds to wait for promise to be fulfilled. +If the promise was not fulfilled in specified timeout the test fails. +When not specified, timeout is set to 2 seconds. + +```php +final class PromiseFulfillsWithInstanceOfTest extends TestCase +{ + /** @test */ + public function promise_fulfills_with_an_instance_of_class(): void + { + $deferred = new Deferred(); + $deferred->resolve(new MyClass); + $this->assertPromiseFulfillsWithInstanceOf($deferred->promise(), MyClass::class); + } +} +``` + +```bash +PHPUnit 8.5.2 by Sebastian Bergmann and contributors. + +F 1 / 1 (100%) + +Time: 180 ms, Memory: 4.00MB + +There was 1 failure: + +1) seregazhuk\React\PromiseTesting\tests\PromiseFulfillsWithWithInstanceOfTest::promise_fulfills_with_an_instance_of_class +Failed asserting that promise fulfills with a value of class MyClass. +``` + ### assertPromiseRejects() `assertPromiseRejects(PromiseInterface $promise, int $timeout = null): void` diff --git a/src/TestCase.php b/src/TestCase.php index e6d5048..4cc4b43 100644 --- a/src/TestCase.php +++ b/src/TestCase.php @@ -4,17 +4,18 @@ use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase as PHPUnitTestCase; +use React\EventLoop\LoopInterface; use React\Promise\PromiseInterface; use Clue\React\Block; use Exception; use React\EventLoop\Factory as LoopFactory; use React\Promise\Timer\TimeoutException; -class TestCase extends PHPUnitTestCase +abstract class TestCase extends PHPUnitTestCase { private const DEFAULT_WAIT_TIMEOUT = 2; - protected $loop; + private $loop; protected function setUp(): void { @@ -34,7 +35,7 @@ public function assertPromiseFulfills(PromiseInterface $promise, int $timeout = try { $this->waitForPromise($promise, $timeout); } catch (TimeoutException $exception) { - $this->fail($failMessage . 'Promise was rejected by timeout.'); + $this->fail($failMessage . 'Promise was cancelled due to timeout.'); } catch (Exception $exception) { $this->fail($failMessage . 'Promise was rejected.'); } @@ -55,7 +56,7 @@ public function assertPromiseFulfillsWith(PromiseInterface $promise, $value, int try { $result = $this->waitForPromise($promise, $timeout); } catch (TimeoutException $exception) { - $this->fail($failMessage . 'Promise was rejected by timeout.'); + $this->fail($failMessage . 'Promise was cancelled due to timeout.'); } catch (Exception $exception) { $this->fail($failMessage . 'Promise was rejected.'); } @@ -63,6 +64,26 @@ public function assertPromiseFulfillsWith(PromiseInterface $promise, $value, int $this->assertEquals($value, $result, $failMessage); } + /** + * @throws AssertionFailedError + */ + public function assertPromiseFulfillsWithInstanceOf(PromiseInterface $promise, string $class, int $timeout = null): void + { + $failMessage = "Failed asserting that promise fulfills with a value of class $class. "; + $result = null; + $this->addToAssertionCount(1); + + try { + $result = $this->waitForPromise($promise, $timeout); + } catch (TimeoutException $exception) { + $this->fail($failMessage . 'Promise was cancelled due to timeout.'); + } catch (Exception $exception) { + $this->fail($failMessage . 'Promise was rejected.'); + } + + $this->assertInstanceOf($class, $result, $failMessage); + } + /** * @param PromiseInterface $promise * @param int|null $timeout @@ -119,4 +140,9 @@ public function waitForPromise(PromiseInterface $promise, int $timeout = null) { return Block\await($promise, $this->loop, $timeout ?: self::DEFAULT_WAIT_TIMEOUT); } + + public function eventLoop(): LoopInterface + { + return $this->loop; + } } diff --git a/tests/PromiseFulfillsTest.php b/tests/PromiseFulfillsTest.php index f3bf569..f2fda3e 100644 --- a/tests/PromiseFulfillsTest.php +++ b/tests/PromiseFulfillsTest.php @@ -10,7 +10,7 @@ final class PromiseFulfillsTest extends TestCase { /** @test */ - public function promise_fulfills() + public function promise_fulfills(): void { try { $deferred = new Deferred(); @@ -31,21 +31,21 @@ public function it_fails_when_promise_doesnt_fulfill_in_a_specified_timeout(): v $deferred = new Deferred(); $deferred->reject(); - $promise = resolve(3, $this->loop); + $promise = resolve($timeToResolve = 3, $this->eventLoop()); - $promise->then(function() use ($deferred){ + $promise->then(static function() use ($deferred) { $deferred->resolve(); }); $this->assertPromiseFulfills($promise, 1); } catch (Exception $exception) { $this->assertRegExp( - '/Failed asserting that promise fulfills. Promise was rejected/', + '/Promise was cancelled due to timeout./', $exception->getMessage() ); $this->assertRegExp( - '/Promise was rejected by timeout/', + '/Promise was cancelled due to timeout/', $exception->getMessage() ); } diff --git a/tests/PromiseFulfillsWithInstanceOfTest.php b/tests/PromiseFulfillsWithInstanceOfTest.php new file mode 100644 index 0000000..337564b --- /dev/null +++ b/tests/PromiseFulfillsWithInstanceOfTest.php @@ -0,0 +1,85 @@ +resolve(new MyClass()); + $this->assertPromiseFulfillsWithInstanceOf($deferred->promise(), MyClass::class, 1); + } catch (Exception $exception) { + $this->assertRegExp( + '/Failed asserting that promise fulfills with a value of class ' . preg_quote(MyClass::class, '/') .'/', + $exception->getMessage() + ); + + $this->assertRegExp( + '/Failed asserting that .+ matches expected .+/', + $exception->getMessage() + ); + } + } + + /** @test */ + public function it_fails_when_promise_rejects(): void + { + try { + $deferred = new Deferred(); + + $deferred->reject(); + $this->assertPromiseFulfillsWithInstanceOf($deferred->promise(), MyClass::class, 1); + } catch (Exception $exception) { + $this->assertRegExp( + '/Failed asserting that promise fulfills with a value of class ' . preg_quote(MyClass::class, '/') .'/', + $exception->getMessage() + ); + + $this->assertRegExp( + '/Promise was rejected/', + $exception->getMessage() + ); + } + } + + /** @test */ + public function it_fails_when_promise_doesnt_fulfill_in_a_specified_timeout(): void + { + try { + $deferred = new Deferred(); + + $deferred->reject(); + $promise = resolve($timeToResolve = 3, $this->eventLoop()); + + $promise->then( + static function() use ($deferred){ + $deferred->resolve(new MyClass()); + } + ); + + $this->assertPromiseFulfillsWithInstanceOf($promise, MyClass::class, 1); + } catch (Exception $exception) { + $this->assertRegExp( + '/Failed asserting that promise fulfills with a value of class ' . preg_quote(MyClass::class, '/') .'/', + $exception->getMessage() + ); + + $this->assertRegExp( + '/Promise was cancelled due to timeout/', + $exception->getMessage() + ); + } + } +} + +final class MyClass { + +} diff --git a/tests/PromiseResolvesWithTest.php b/tests/PromiseFulfillsWithTest.php similarity index 85% rename from tests/PromiseResolvesWithTest.php rename to tests/PromiseFulfillsWithTest.php index 92b9643..1325351 100644 --- a/tests/PromiseResolvesWithTest.php +++ b/tests/PromiseFulfillsWithTest.php @@ -7,7 +7,7 @@ use function React\Promise\Timer\resolve; use seregazhuk\React\PromiseTesting\TestCase; -final class PromiseResolvesWithTest extends TestCase +final class PromiseFulfillsWithTest extends TestCase { /** @test */ public function promise_fulfills_with_a_specified_value(): void @@ -58,11 +58,13 @@ public function it_fails_when_promise_doesnt_fulfill_in_a_specified_timeout(): v $deferred = new Deferred(); $deferred->reject(); - $promise = resolve(3, $this->loop); + $promise = resolve($timeToResolve = 3, $this->eventLoop()); - $promise->then(function() use ($deferred){ - $deferred->resolve(); - }); + $promise->then( + static function () use ($deferred) { + $deferred->resolve(); + } + ); $this->assertPromiseFulfillsWith($promise, 1, 1); } catch (Exception $exception) { @@ -72,7 +74,7 @@ public function it_fails_when_promise_doesnt_fulfill_in_a_specified_timeout(): v ); $this->assertRegExp( - '/Promise was rejected by timeout/', + '/Promise was cancelled due to timeout/', $exception->getMessage() ); } diff --git a/tests/WaitForPromiseToFulfillTest.php b/tests/WaitForPromiseToFulfillTest.php index c24f3fe..85d0b74 100644 --- a/tests/WaitForPromiseToFulfillTest.php +++ b/tests/WaitForPromiseToFulfillTest.php @@ -15,7 +15,7 @@ public function promise_resolves(): void $deferred = new Deferred(); $deferred->reject(new Exception()); - $value = $this->waitForPromiseToFulfill($deferred->promise()); + $this->waitForPromiseToFulfill($deferred->promise()); } catch (Exception $exception) { $this->assertRegExp( '/Failed to fulfill a promise. It was rejected with Exception/',