├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── coverage-checker.php ├── phpunit.xml ├── src ├── AssertsPromise.php └── TestCase.php └── tests ├── AboutPromiseTest.php ├── PromiseFulfillsTest.php ├── PromiseFulfillsWithInstanceOfTest.php ├── PromiseFulfillsWithTest.php ├── PromiseRejectsTest.php ├── PromiseRejectsWithTest.php └── WaitForPromiseToFulfillTest.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | PHP: true 3 | 4 | engines: 5 | phpcodesniffer: 6 | enabled: true 7 | phpmd: 8 | enabled: true 9 | 10 | 11 | ratings: 12 | paths: 13 | - src/** 14 | - "*.php" 15 | 16 | exclude_paths: 17 | - tests/**/* 18 | - vendor/**/* 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | vendor/ 3 | composer.lock 4 | .phpunit.result.cache -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | addons: 6 | code_climate: 7 | repo_token: 8 | secure: "GxuXRKt6H2Df0xry9BhCnKzRxkPeBKvNrzpWj39GH9gszEjPfVRmeboREdEqwzNmJrqYluR691Jp1O9DsWqmiuN0Gq4Rrf+6EheJ6FeLrMsGpzMiJOQXCIvFD1eMUkJRLbO0yAy19jmFJwtFux6P+J76DpwIoVx57PR8kayMstQ4bDsTJp2lAsXIzwHNostVXjRKLIczjSd8DtQnegR6ehETG6us1RNnHTGQeWbhd8gnZ0oewiWNRfPQ6Lmhx3ZIrIxz4kdZi+Km58MH0U+ffZK0k1aBfLcGhxtCANXjvoc/aC3UHqEO19YPKVGxDyzq2aGj08iPT3a9E2E8Frw3N9MAxtY7sjbgRgNSvrg2gIdUyxIvkd+VbCZUsaKyRZubYinqnbwCImyVAKx0hBiTR/OwYbnry+3h9Rk6FBQ/enumsqRrnjazUcW0GeW3OKjOe4znzOmevt/GvWer19SxdzS69yRfjGwZE6ur9Se8wjpF42nikqPOEC7zqEVWVSz0vPSH3Qc8ckaaVvQuo/rRciq+32MxuHzROFJiL/lKUcvm+b2DRt+HtQ0PkPp6S0VNuDrfTioABaW9Sz97RRSdd0jD3Sarg0SApb8LwqTu5mHtv/UBoU6AsBZ0VuQVnhqtBB608iRKLTktK8UyoXSMhPNy5eSM8VYcDbp/da3ILqU=" 9 | 10 | jobs: 11 | include: 12 | - stage: "PHP8.0" 13 | php: 8.0 14 | script: 15 | - composer update -n --prefer-dist --no-suggest 16 | - composer dump-autoload 17 | - composer ci:tests 18 | 19 | after_script: 20 | - vendor/bin/test-reporter 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## 0.6.1 - 2021-04-22 5 | ### Fixed: 6 | - False positive `assertPromiseRejectsWith()` 7 | 8 | 9 | ## 0.6.0 - 2020-07-03 10 | ### Added: 11 | - New assertions `assertTrueAboutPromise()` and `assertFalseAboutPromise()` (#21 by @ace411) 12 | 13 | ## 0.5.0 - 2020-05-22 14 | ### Added: 15 | - trait `AssertsPromise` 16 | 17 | ## 0.4.0 - 2020-03-09 18 | ### Changed: 19 | - `TestCase` is now abstract 20 | ### Added: 21 | - `assertPromiseFulfillsWithInstanceOf()` to check class of the resolution value 22 | 23 | ## 0.3.0 - 2020-03-08 24 | ### Updated: 25 | - Dependencies 26 | - Added types 27 | 28 | ## 0.2.2 - 2019-04-06 29 | ### Fixed: 30 | - use vendor PHPUnit when running TravisCI 31 | 32 | ## 0.2.1 - 2018-09-14 33 | ### Fixed: 34 | - assertions counter 35 | 36 | ## 0.2.0 - 2018-06-26 37 | ### Updated: 38 | - dependencies and php version changed to 7.2 39 | 40 | ## 0.1.5 - 2018-03-01 41 | ### Fixed: 42 | - wrong assertions count 43 | 44 | ## 0.1.4 - 2018-02-16 45 | ### Added: 46 | - support for php7 47 | 48 | ## 0.1.3 - 2018-02-10 49 | ### Fixed: 50 | - method names (replace verb `resolve` with `fulfill`) 51 | 52 | ## 0.1.3 - 2018-02-10 53 | ### Fixed: 54 | - method names (replace verb `resolve` with `fulfill`) 55 | 56 | ## 0.1.2 - 2017-12-01 57 | ### Fixed: 58 | - increase assertions count when checking that promise resolves/rejects 59 | 60 | ## 0.1.1 - 2017-11-12 61 | ### Added: 62 | - some helpers to wait for promises 63 | 64 | ## 0.1.0 - 2017-11-10 65 | - First tagged release 66 | 67 | ## 0.0.0 - 2017-11-08 68 | - First initial commit 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017 Sergey Zhuk 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | software and associated documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom 8 | the Software is furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 13 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 14 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactPHP Promises Testing 2 | A library that provides a set of convenient assertions for testing ReactPHP promises. 3 | Under the hood uses [clue/php-block-react](https://github.com/clue/php-block-react) to block promises. 4 | 5 | [![Build Status](https://travis-ci.org/seregazhuk/php-react-promise-testing.svg?branch=master)](https://travis-ci.org/seregazhuk/php-react-promise-testing) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/689230cdae09d2e32600/maintainability)](https://codeclimate.com/github/seregazhuk/php-react-promise-testing/maintainability) 7 | [![Test Coverage](https://api.codeclimate.com/v1/badges/689230cdae09d2e32600/test_coverage)](https://codeclimate.com/github/seregazhuk/php-react-promise-testing/test_coverage) 8 | [![Total Downloads](https://poser.pugx.org/seregazhuk/react-promise-testing/downloads)](//packagist.org/packages/seregazhuk/react-promise-testing) 9 | 10 | When testing asynchronous code and promises things can be a bit tricky. This library provides a set of convenient 11 | assertions for testing ReactPHP promises. 12 | 13 | **Table of Contents** 14 | - [Installation](#installation) 15 | - [Quick Start](#quick-start) 16 | - [Assertions](#assertions) 17 | - [assertPromiseFulfills()](#assertpromisefulfills) 18 | - [assertPromiseFulfillsWith()](#assertpromisefulfillswith) 19 | - [assertPromiseFulfillsWithInstanceOf()](#assertpromisefulfillswithinstanceof) 20 | - [assertPromiseRejects()](#assertpromiserejects()) 21 | - [assertPromiseRejectsWith()](#assertpromiserejectswith) 22 | - [assertTrueAboutPromise()](#asserttrueaboutpromise) 23 | - [assertFalseAboutPromise()](#assertfalseaboutpromise) 24 | 25 | - [Helpers](#helpers) 26 | - [waitForPromiseToFulfill()](#waitforpromisetofulfill) 27 | - [waitForPromise()](#waitforpromise) 28 | 29 | ## Installation 30 | 31 | ### Dependencies 32 | Library requires PHP 8.0 or above. 33 | 34 | The recommended way to install this library is via [Composer](https://getcomposer.org). 35 | [New to Composer?](https://getcomposer.org/doc/00-intro.md) 36 | 37 | See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. 38 | 39 | ``` 40 | composer require seregazhuk/react-promise-testing --dev 41 | ``` 42 | 43 | ## Quick Start 44 | Use the trait `seregazhuk\React\PromiseTesting\AssertsPromise` or extend your 45 | test classes from `seregazhuk\React\PromiseTesting\TestCase` class, 46 | which itself extends PHPUnit `TestCase`. 47 | 48 | ```php 49 | final class MyTest extends TestCase 50 | { 51 | /** @test */ 52 | public function promise_fulfills_with_a_response_object() 53 | { 54 | $browser = new Clue\React\Buzz\Browser($this->eventLoop()); 55 | $promise = $browser->get('http://www.google.com/'); 56 | $this->assertPromiseFulfillsWithInstanceOf($promise, ResponseInterface::class); 57 | } 58 | } 59 | ``` 60 | 61 | Using the trait: 62 | 63 | ```php 64 | 65 | use PHPUnit\Framework\TestCase; 66 | use seregazhuk\React\PromiseTesting\AssertsPromise; 67 | 68 | final class MyTest extends TestCase 69 | { 70 | use AssertsPromise; 71 | 72 | /** @test */ 73 | public function promise_fulfills_with_a_response_object() 74 | { 75 | $browser = new Clue\React\Buzz\Browser($this->eventLoop()); 76 | $promise = $browser->get('http://www.google.com/'); 77 | $this->assertPromiseFulfillsWithInstanceOf($promise, ResponseInterface::class); 78 | } 79 | } 80 | ``` 81 | 82 | Test above checks that a specified promise fulfills with an instance of `ResponseInterface`. 83 | 84 | ## Event loop 85 | 86 | To make promise assertions we need to run the loop. Before each test a new instance of the event loop 87 | is being created (inside `setUp()` method). If you need the loop to build your dependencies you **should** 88 | use `eventLoop()` method to retrieve it. 89 | 90 | 91 | 92 | ## Assertions 93 | 94 | ### assertPromiseFulfills() 95 | 96 | `public function assertPromiseFulfills(PromiseInterface $promise, int $timeout = null): void` 97 | 98 | The test fails if the `$promise` rejects. 99 | 100 | You can specify `$timeout` in seconds to wait for promise to be resolved. 101 | If the promise was not fulfilled in specified timeout the test fails. When not specified, timeout is set to 2 seconds. 102 | 103 | ```php 104 | final class PromiseFulfillsTest extends TestCase 105 | { 106 | /** @test */ 107 | public function promise_fulfills(): void 108 | { 109 | $deferred = new Deferred(); 110 | $deferred->reject(); 111 | $this->assertPromiseFulfills($deferred->promise(), 1); 112 | } 113 | } 114 | ``` 115 | 116 | ```bash 117 | PHPUnit 8.5.2 by Sebastian Bergmann and contributors. 118 | 119 | F 1 / 1 (100%) 120 | 121 | Time: 189 ms, Memory: 4.00MB 122 | 123 | There was 1 failure: 124 | 125 | 1) seregazhuk\React\PromiseTesting\tests\PromiseFulfillTest::promise_fulfills 126 | Failed asserting that promise fulfills. Promise was rejected. 127 | ``` 128 | 129 | ### assertPromiseFulfillsWith() 130 | 131 | `assertPromiseFulfillsWith(PromiseInterface $promise, $value, int $timeout = null): void` 132 | 133 | The test fails if the `$promise` doesn't fulfills with a specified `$value`. 134 | 135 | You can specify `$timeout` in seconds to wait for promise to be fulfilled. 136 | If the promise was not fulfilled in specified timeout the test fails. 137 | When not specified, timeout is set to 2 seconds. 138 | 139 | ```php 140 | final class PromiseFulfillsWithTest extends TestCase 141 | { 142 | /** @test */ 143 | public function promise_fulfills_with_a_specified_value(): void 144 | { 145 | $deferred = new Deferred(); 146 | $deferred->resolve(1234); 147 | $this->assertPromiseFulfillsWith($deferred->promise(), 1); 148 | } 149 | } 150 | ``` 151 | 152 | ```bash 153 | PHPUnit 8.5.2 by Sebastian Bergmann and contributors. 154 | 155 | F 1 / 1 (100%) 156 | 157 | Time: 180 ms, Memory: 4.00MB 158 | 159 | There was 1 failure: 160 | 161 | 1) seregazhuk\React\PromiseTesting\tests\PromiseFulfillsWithTest::promise_fulfills_with_a_specified_value 162 | Failed asserting that promise fulfills with a specified value. 163 | Failed asserting that 1234 matches expected 1. 164 | ``` 165 | 166 | ### assertPromiseFulfillsWithInstanceOf() 167 | 168 | `assertPromiseFulfillsWithInstanceOf(PromiseInterface $promise, string $class, int $timeout = null): void` 169 | 170 | The test fails if the `$promise` doesn't fulfills with an instance of specified `$class`. 171 | 172 | You can specify `$timeout` in seconds to wait for promise to be fulfilled. 173 | If the promise was not fulfilled in specified timeout the test fails. 174 | When not specified, timeout is set to 2 seconds. 175 | 176 | ```php 177 | final class PromiseFulfillsWithInstanceOfTest extends TestCase 178 | { 179 | /** @test */ 180 | public function promise_fulfills_with_an_instance_of_class(): void 181 | { 182 | $deferred = new Deferred(); 183 | $deferred->resolve(new MyClass); 184 | $this->assertPromiseFulfillsWithInstanceOf($deferred->promise(), MyClass::class); 185 | } 186 | } 187 | ``` 188 | 189 | ```bash 190 | PHPUnit 8.5.2 by Sebastian Bergmann and contributors. 191 | 192 | F 1 / 1 (100%) 193 | 194 | Time: 180 ms, Memory: 4.00MB 195 | 196 | There was 1 failure: 197 | 198 | 1) seregazhuk\React\PromiseTesting\tests\PromiseFulfillsWithWithInstanceOfTest::promise_fulfills_with_an_instance_of_class 199 | Failed asserting that promise fulfills with a value of class MyClass. 200 | ``` 201 | 202 | ### assertPromiseRejects() 203 | `assertPromiseRejects(PromiseInterface $promise, int $timeout = null): void` 204 | 205 | The test fails if the `$promise` fulfills. 206 | 207 | You can specify `$timeout` in seconds to wait for promise to be resolved. 208 | If the promise was not fulfilled in specified timeout, it rejects with `React\Promise\Timer\TimeoutException`. 209 | When not specified, timeout is set to 2 seconds. 210 | 211 | ```php 212 | final class PromiseRejectsTest extends TestCase 213 | { 214 | /** @test */ 215 | public function promise_rejects(): void 216 | { 217 | $deferred = new Deferred(); 218 | $deferred->resolve(); 219 | $this->assertPromiseRejects($deferred->promise()); 220 | } 221 | } 222 | ``` 223 | 224 | ```bash 225 | PHPUnit 8.5.2 by Sebastian Bergmann and contributors. 226 | 227 | F 1 / 1 (100%) 228 | 229 | Time: 175 ms, Memory: 4.00MB 230 | 231 | There was 1 failure: 232 | 233 | 1) seregazhuk\React\PromiseTesting\tests\PromiseRejectsTest::promise_rejects 234 | Failed asserting that promise rejects. Promise was fulfilled. 235 | ``` 236 | 237 | ### assertPromiseRejectsWith() 238 | `assertPromiseRejectsWith(PromiseInterface $promise, string $reasonExceptionClass, int $timeout = null): void` 239 | 240 | The test fails if the `$promise` doesn't reject with a specified exception class. 241 | 242 | You can specify `$timeout` in seconds to wait for promise to be resolved. 243 | If the promise was not fulfilled in specified timeout, it rejects with `React\Promise\Timer\TimeoutException`. 244 | When not specified, timeout is set to 2 seconds. 245 | 246 | ```php 247 | final class PromiseRejectsWithTest extends TestCase 248 | { 249 | /** @test */ 250 | public function promise_rejects_with_a_specified_reason(): void 251 | { 252 | $deferred = new Deferred(); 253 | $deferred->reject(new \LogicException()); 254 | $this->assertPromiseRejectsWith($deferred->promise(), \InvalidArgumentException::class); 255 | } 256 | } 257 | ``` 258 | 259 | ```bash 260 | PHPUnit 8.5.2 by Sebastian Bergmann and contributors. 261 | 262 | F 1 / 1 (100%) 263 | 264 | Time: 136 ms, Memory: 4.00MB 265 | 266 | There was 1 failure: 267 | 268 | 1) seregazhuk\React\PromiseTesting\tests\PromiseRejectsWithTest::promise_rejects_with_a_specified_reason 269 | Failed asserting that promise rejects with a specified reason. 270 | Failed asserting that LogicException Object (...) is an instance of class "InvalidArgumentException". 271 | ``` 272 | 273 | ### assertTrueAboutPromise() 274 | `assertTrueAboutPromise(PromiseInterface $promise, callable $predicate, int $timeout = null): void` 275 | 276 | The test fails if the value encapsulated in the Promise does not conform to an arbitrary predicate. 277 | 278 | You can specify `$timeout` in seconds to wait for promise to be resolved. 279 | If the promise was not fulfilled in specified timeout, it rejects with `React\Promise\Timer\TimeoutException`. 280 | When not specified, timeout is set to 2 seconds. 281 | 282 | ```php 283 | final class AssertTrueAboutPromiseTest extends TestCase 284 | { 285 | /** @test */ 286 | public function promise_encapsulates_integer(): void 287 | { 288 | $deferred = new Deferred(); 289 | $deferred->resolve(23); 290 | 291 | $this->assertTrueAboutPromise($deferred->promise(), function ($val) { 292 | return is_object($val); 293 | }); 294 | } 295 | } 296 | ``` 297 | 298 | ```bash 299 | PHPUnit 8.5.2 by Sebastian Bergmann and contributors. 300 | 301 | F 1 / 1 (100%) 302 | 303 | Time: 136 ms, Memory: 4.00MB 304 | 305 | There was 1 failure: 306 | 307 | 1) seregazhuk\React\PromiseTesting\tests\AssertTrueAboutPromiseTest::promise_encapsulates_integer 308 | Failed asserting that false is true. 309 | ``` 310 | 311 | ### assertFalseAboutPromise() 312 | `assertFalseAboutPromise(PromiseInterface $promise, callable $predicate, int $timeout = null): void` 313 | 314 | The test fails if the value encapsulated in the Promise conforms to an arbitrary predicate. 315 | 316 | You can specify `$timeout` in seconds to wait for promise to be resolved. 317 | If the promise was not fulfilled in specified timeout, it rejects with `React\Promise\Timer\TimeoutException`. 318 | When not specified, timeout is set to 2 seconds. 319 | 320 | ```php 321 | final class AssertFalseAboutPromiseTest extends TestCase 322 | { 323 | /** @test */ 324 | public function promise_encapsulates_object(): void 325 | { 326 | $deferred = new Deferred(); 327 | $deferred->resolve(23); 328 | 329 | $this->assertFalseAboutPromise($deferred->promise(), function ($val) { 330 | return is_int($val); 331 | }); 332 | } 333 | } 334 | ``` 335 | 336 | ```bash 337 | PHPUnit 8.5.2 by Sebastian Bergmann and contributors. 338 | 339 | F 1 / 1 (100%) 340 | 341 | Time: 136 ms, Memory: 4.00MB 342 | 343 | There was 1 failure: 344 | 345 | 1) seregazhuk\React\PromiseTesting\tests\AssertFalseAboutPromiseTest::promise_encapsulates_object 346 | Failed asserting that true is false. 347 | ``` 348 | 349 | ## Helpers 350 | 351 | ### waitForPromiseToFulfill() 352 | `function waitForPromiseToFulfill(PromiseInterface $promise, int $timeout = null)`. 353 | 354 | This helper can be used when you want to resolve a promise and get the resolution value. 355 | 356 | Tries to resolve a `$promise` in a specified `$timeout` seconds and returns resolved value. If `$timeout` is not 357 | set uses 2 seconds by default. The test fails if the `$promise` doesn't fulfill. 358 | 359 | ```php 360 | final class WaitForPromiseToFulfillTest extends TestCase 361 | { 362 | /** @test */ 363 | public function promise_fulfills(): void 364 | { 365 | $deferred = new Deferred(); 366 | 367 | $deferred->reject(new \Exception()); 368 | $value = $this->waitForPromiseToFulfill($deferred->promise()); 369 | } 370 | } 371 | ``` 372 | 373 | ```bash 374 | PHPUnit 8.5.2 by Sebastian Bergmann and contributors. 375 | 376 | F 1 / 1 (100%) 377 | 378 | Time: 223 ms, Memory: 6.00MB 379 | 380 | There was 1 failure: 381 | 382 | 1) seregazhuk\React\PromiseTesting\tests\WaitForPromiseToFulfillTest::promise_fulfills 383 | Failed to fulfill a promise. It was rejected with Exception. 384 | ``` 385 | 386 | ### waitForPromise() 387 | `function waitForPromise(PromiseInterface $promise, int $timeout = null)`. 388 | 389 | Tries to resolve a specified `$promise` in a specified `$timeout` seconds. If `$timeout` is not set uses 2 390 | seconds by default. If the promise fulfills returns a resolution value, otherwise throws an exception. If the 391 | promise rejects throws the rejection reason, if the promise doesn't fulfill in a specified `$timeout` throws 392 | `React\Promise\Timer\TimeoutException`. 393 | 394 | This helper can be useful when you need to get the value from the fulfilled promise in a synchronous way: 395 | 396 | ```php 397 | $value = $this->waitForPromise($cache->get('key')); 398 | ``` 399 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seregazhuk/react-promise-testing", 3 | "description": "PHPUnit-based library for testing ReactPHP promises", 4 | "keywords": [ 5 | "Testing", 6 | "ReactPHP", 7 | "promise", 8 | "test" 9 | ], 10 | "homepage": "https://github.com/seregazhuk/php-react-promise-testing", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Sergey Zhuk", 15 | "email": "seregazhuk88@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=8.0", 20 | "react/promise": "^2.8", 21 | "phpunit/phpunit": "^9.5", 22 | "phpunit/php-code-coverage": "^9.2", 23 | "clue/block-react": "^1.4" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "seregazhuk\\React\\PromiseTesting\\": "src/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "seregazhuk\\React\\PromiseTesting\\tests\\": "tests/" 33 | } 34 | }, 35 | "scripts": { 36 | "ci:tests": "vendor/bin/phpunit tests" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /coverage-checker.php: -------------------------------------------------------------------------------- 1 | xpath('//metrics'); 16 | $totalElements = 0; 17 | $checkedElements = 0; 18 | 19 | foreach ($metrics as $metric) { 20 | $totalElements += (int) $metric['elements']; 21 | $checkedElements += (int) $metric['coveredelements']; 22 | } 23 | 24 | $coverage = ($checkedElements / $totalElements) * 100; 25 | 26 | if ($coverage < $percentage) { 27 | echo 'Code coverage is ' . $coverage . '%, which is below the accepted ' . $percentage . '%' . PHP_EOL; 28 | exit(1); 29 | } 30 | 31 | echo 'Code coverage is ' . $coverage . '% - OK!' . PHP_EOL; 32 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src/ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/AssertsPromise.php: -------------------------------------------------------------------------------- 1 | addToAssertionCount(1); 28 | 29 | try { 30 | $this->waitForPromise($promise, $timeout); 31 | } catch (TimeoutException $exception) { 32 | $this->fail($failMessage . 'Promise was cancelled due to timeout.'); 33 | } catch (Exception $exception) { 34 | $this->fail($failMessage . 'Promise was rejected.'); 35 | } 36 | } 37 | 38 | /** 39 | * @param PromiseInterface $promise 40 | * @param mixed $value 41 | * @param int|null $timeout 42 | * @throws AssertionFailedError 43 | */ 44 | public function assertPromiseFulfillsWith(PromiseInterface $promise, $value, int $timeout = null): void 45 | { 46 | $failMessage = 'Failed asserting that promise fulfills with a specified value. '; 47 | $result = null; 48 | $this->addToAssertionCount(1); 49 | 50 | try { 51 | $result = $this->waitForPromise($promise, $timeout); 52 | } catch (TimeoutException $exception) { 53 | $this->fail($failMessage . 'Promise was cancelled due to timeout.'); 54 | } catch (Exception $exception) { 55 | $this->fail($failMessage . 'Promise was rejected.'); 56 | } 57 | 58 | $this->assertEquals($value, $result, $failMessage); 59 | } 60 | 61 | /** 62 | * @throws AssertionFailedError 63 | */ 64 | public function assertPromiseFulfillsWithInstanceOf(PromiseInterface $promise, string $class, int $timeout = null): void 65 | { 66 | $failMessage = "Failed asserting that promise fulfills with a value of class $class. "; 67 | $result = null; 68 | $this->addToAssertionCount(1); 69 | 70 | try { 71 | $result = $this->waitForPromise($promise, $timeout); 72 | } catch (TimeoutException $exception) { 73 | $this->fail($failMessage . 'Promise was cancelled due to timeout.'); 74 | } catch (Exception $exception) { 75 | $this->fail($failMessage . 'Promise was rejected.'); 76 | } 77 | 78 | $this->assertInstanceOf($class, $result, $failMessage); 79 | } 80 | 81 | /** 82 | * @param PromiseInterface $promise 83 | * @param int|null $timeout 84 | * @throws AssertionFailedError 85 | */ 86 | public function assertPromiseRejects(PromiseInterface $promise, int $timeout = null): void 87 | { 88 | $this->addToAssertionCount(1); 89 | 90 | try { 91 | $this->waitForPromise($promise, $timeout); 92 | } catch (Exception $exception) { 93 | return; 94 | } 95 | 96 | $this->fail('Failed asserting that promise rejects. Promise was fulfilled.'); 97 | } 98 | 99 | /** 100 | * @throws AssertionFailedError 101 | */ 102 | public function assertPromiseRejectsWith(PromiseInterface $promise, string $reasonExceptionClass, int $timeout = null): void 103 | { 104 | try { 105 | $this->waitForPromise($promise, $timeout); 106 | } catch (Exception $reason) { 107 | $this->assertInstanceOf( 108 | $reasonExceptionClass, $reason, 'Failed asserting that promise rejects with a specified reason.' 109 | ); 110 | 111 | return; 112 | } 113 | 114 | $this->fail('Failed asserting that promise rejects. Promise was fulfilled.'); 115 | } 116 | 117 | /** 118 | * @throws Exception 119 | * @return mixed 120 | */ 121 | public function waitForPromiseToFulfill(PromiseInterface $promise, int $timeout = null) 122 | { 123 | try { 124 | return $this->waitForPromise($promise, $timeout); 125 | } catch (Exception $exception) { 126 | $reason = get_class($exception); 127 | $this->fail("Failed to fulfill a promise. It was rejected with {$reason}."); 128 | } 129 | } 130 | 131 | /** 132 | * @return mixed 133 | * @throws Exception 134 | */ 135 | public function waitForPromise(PromiseInterface $promise, int $timeout = null) 136 | { 137 | return Block\await($promise, $this->eventLoop(), $timeout ?: $this->defaultWaitTimeout); 138 | } 139 | 140 | public function eventLoop(): LoopInterface 141 | { 142 | if (! $this->loop) { 143 | $this->loop = LoopFactory::create(); 144 | } 145 | 146 | return $this->loop; 147 | } 148 | 149 | /** 150 | * @param PromiseInterface $promise 151 | * @param callable $predicate 152 | * @param int|null $timeout 153 | * @throws AssertionFailedError 154 | */ 155 | public function assertTrueAboutPromise( 156 | PromiseInterface $promise, 157 | callable $predicate, 158 | int $timeout = null 159 | ): void { 160 | $this->assertAboutPromise($promise, $predicate, $timeout); 161 | } 162 | 163 | /** 164 | * @param PromiseInterface $promise 165 | * @param callable $predicate 166 | * @param int|null $timeout 167 | * @throws AssertionFailedError 168 | */ 169 | public function assertFalseAboutPromise( 170 | PromiseInterface $promise, 171 | callable $predicate, 172 | int $timeout = null 173 | ): void { 174 | $this->assertAboutPromise($promise, $predicate, $timeout, false); 175 | } 176 | 177 | /** 178 | * @param PromiseInterface $promise 179 | * @param callable $predicate 180 | * @param int|null $timeout 181 | * @param bool $assertTrue 182 | * @throws AssertionFailedError 183 | */ 184 | private function assertAboutPromise( 185 | PromiseInterface $promise, 186 | callable $predicate, 187 | int $timeout = null, 188 | bool $assertTrue = true 189 | ): void { 190 | $result = $assertTrue ? false : true; 191 | $this->addToAssertionCount(1); 192 | 193 | try { 194 | $result = $predicate($this->waitForPromise($promise, $timeout)); 195 | } catch (TimeoutException $exception) { 196 | $this->fail('Promise was cancelled due to timeout'); 197 | } catch (Exception $exception) { 198 | $this->fail('Failed asserting that promise was fulfilled. Promise was rejected'); 199 | } 200 | 201 | $assertTrue ? $this->assertTrue($result) : $this->assertFalse($result); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/TestCase.php: -------------------------------------------------------------------------------- 1 | eventLoop(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/AboutPromiseTest.php: -------------------------------------------------------------------------------- 1 | resolve(23); 17 | 18 | $snd = new Deferred; 19 | $snd->reject(new Exception('An error')); 20 | 21 | $thd = new Deferred; 22 | $thd->reject('Another error'); 23 | 24 | $this->assertTrueAboutPromise($fst->promise(), function ($val): bool { 25 | return is_int($val) && $val > 20; 26 | }); 27 | $this->assertTrueAboutPromise($fst->promise(), 'is_string'); 28 | 29 | $this->assertTrueAboutPromise($snd->promise(), 'is_string'); 30 | 31 | $this->assertTrueAboutPromise($thd->promise(), 'is_string'); 32 | } catch (Exception $exception) { 33 | $this->assertMatchesRegularExpression( 34 | '/Failed asserting that .+/', 35 | $exception->getMessage() 36 | ); 37 | } 38 | } 39 | 40 | /** @test */ 41 | public function predicate_function_reveals_what_is_false_about_promise(): void 42 | { 43 | try { 44 | $fst = new Deferred; 45 | $fst->resolve(23); 46 | 47 | $snd = new Deferred; 48 | $snd->reject(new Exception('An error')); 49 | 50 | $thd = new Deferred; 51 | $thd->reject('Another error'); 52 | 53 | $this->assertFalseAboutPromise($fst->promise(), function ($val): bool { 54 | return is_int($val) && $val > 20; 55 | }); 56 | $this->assertFalseAboutPromise($fst->promise(), 'is_string'); 57 | 58 | $this->assertFalseAboutPromise($snd->promise(), 'is_string'); 59 | 60 | $this->assertFalseAboutPromise($thd->promise(), 'is_int'); 61 | } catch (Exception $exception) { 62 | $this->assertMatchesRegularExpression( 63 | '/Failed asserting that .+/', 64 | $exception->getMessage() 65 | ); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/PromiseFulfillsTest.php: -------------------------------------------------------------------------------- 1 | reject(); 18 | $this->assertPromiseFulfills($deferred->promise(), 1); 19 | } catch (Exception $exception) { 20 | $this->assertMatchesRegularExpression( 21 | '/Failed asserting that promise fulfills. Promise was rejected/', 22 | $exception->getMessage() 23 | ); 24 | } 25 | } 26 | 27 | /** @test */ 28 | public function it_fails_when_promise_doesnt_fulfill_in_a_specified_timeout(): void 29 | { 30 | try { 31 | $deferred = new Deferred(); 32 | 33 | $deferred->reject(); 34 | $promise = resolve($timeToResolve = 3, $this->eventLoop()); 35 | 36 | $promise->then(static function() use ($deferred) { 37 | $deferred->resolve(); 38 | }); 39 | 40 | $this->assertPromiseFulfills($promise, 1); 41 | } catch (Exception $exception) { 42 | $this->assertMatchesRegularExpression( 43 | '/Promise was cancelled due to timeout./', 44 | $exception->getMessage() 45 | ); 46 | 47 | $this->assertMatchesRegularExpression( 48 | '/Promise was cancelled due to timeout/', 49 | $exception->getMessage() 50 | ); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/PromiseFulfillsWithInstanceOfTest.php: -------------------------------------------------------------------------------- 1 | resolve(new MyClass()); 18 | $this->assertPromiseFulfillsWithInstanceOf($deferred->promise(), MyClass::class, 1); 19 | } catch (Exception $exception) { 20 | $this->assertMatchesRegularExpression( 21 | '/Failed asserting that promise fulfills with a value of class ' . preg_quote(MyClass::class, '/') .'/', 22 | $exception->getMessage() 23 | ); 24 | 25 | $this->assertMatchesRegularExpression( 26 | '/Failed asserting that .+ matches expected .+/', 27 | $exception->getMessage() 28 | ); 29 | } 30 | } 31 | 32 | /** @test */ 33 | public function it_fails_when_promise_rejects(): void 34 | { 35 | try { 36 | $deferred = new Deferred(); 37 | 38 | $deferred->reject(); 39 | $this->assertPromiseFulfillsWithInstanceOf($deferred->promise(), MyClass::class, 1); 40 | } catch (Exception $exception) { 41 | $this->assertMatchesRegularExpression( 42 | '/Failed asserting that promise fulfills with a value of class ' . preg_quote(MyClass::class, '/') .'/', 43 | $exception->getMessage() 44 | ); 45 | 46 | $this->assertMatchesRegularExpression( 47 | '/Promise was rejected/', 48 | $exception->getMessage() 49 | ); 50 | } 51 | } 52 | 53 | /** @test */ 54 | public function it_fails_when_promise_doesnt_fulfill_in_a_specified_timeout(): void 55 | { 56 | try { 57 | $deferred = new Deferred(); 58 | 59 | $deferred->reject(); 60 | $promise = resolve($timeToResolve = 3, $this->eventLoop()); 61 | 62 | $promise->then( 63 | static function() use ($deferred){ 64 | $deferred->resolve(new MyClass()); 65 | } 66 | ); 67 | 68 | $this->assertPromiseFulfillsWithInstanceOf($promise, MyClass::class, 1); 69 | } catch (Exception $exception) { 70 | $this->assertMatchesRegularExpression( 71 | '/Failed asserting that promise fulfills with a value of class ' . preg_quote(MyClass::class, '/') .'/', 72 | $exception->getMessage() 73 | ); 74 | 75 | $this->assertMatchesRegularExpression( 76 | '/Promise was cancelled due to timeout/', 77 | $exception->getMessage() 78 | ); 79 | } 80 | } 81 | } 82 | 83 | final class MyClass { 84 | 85 | } 86 | -------------------------------------------------------------------------------- /tests/PromiseFulfillsWithTest.php: -------------------------------------------------------------------------------- 1 | resolve(1234); 19 | $this->assertPromiseFulfillsWith($deferred->promise(), 1); 20 | } catch (Exception $exception) { 21 | $this->assertMatchesRegularExpression( 22 | '/Failed asserting that promise fulfills with a specified value/', 23 | $exception->getMessage() 24 | ); 25 | 26 | $this->assertMatchesRegularExpression( 27 | '/Failed asserting that .+ matches expected .+/', 28 | $exception->getMessage() 29 | ); 30 | } 31 | } 32 | 33 | /** @test */ 34 | public function it_fails_when_promise_rejects(): void 35 | { 36 | try { 37 | $deferred = new Deferred(); 38 | 39 | $deferred->reject(); 40 | $this->assertPromiseFulfillsWith($deferred->promise(), 1); 41 | } catch (Exception $exception) { 42 | $this->assertMatchesRegularExpression( 43 | '/Failed asserting that promise fulfills with a specified value/', 44 | $exception->getMessage() 45 | ); 46 | 47 | $this->assertMatchesRegularExpression( 48 | '/Promise was rejected/', 49 | $exception->getMessage() 50 | ); 51 | } 52 | } 53 | 54 | /** @test */ 55 | public function it_fails_when_promise_doesnt_fulfill_in_a_specified_timeout(): void 56 | { 57 | try { 58 | $deferred = new Deferred(); 59 | 60 | $deferred->reject(); 61 | $promise = resolve($timeToResolve = 3, $this->eventLoop()); 62 | 63 | $promise->then( 64 | static function () use ($deferred) { 65 | $deferred->resolve(); 66 | } 67 | ); 68 | 69 | $this->assertPromiseFulfillsWith($promise, 1, 1); 70 | } catch (Exception $exception) { 71 | $this->assertMatchesRegularExpression( 72 | '/Failed asserting that promise fulfills with a specified value/', 73 | $exception->getMessage() 74 | ); 75 | 76 | $this->assertMatchesRegularExpression( 77 | '/Promise was cancelled due to timeout/', 78 | $exception->getMessage() 79 | ); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/PromiseRejectsTest.php: -------------------------------------------------------------------------------- 1 | resolve(); 17 | $this->assertPromiseRejects($deferred->promise()); 18 | } catch (Exception $exception) { 19 | $this->assertMatchesRegularExpression( 20 | '/Failed asserting that promise rejects. Promise was fulfilled/', 21 | $exception->getMessage() 22 | ); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/PromiseRejectsWithTest.php: -------------------------------------------------------------------------------- 1 | reject(new \InvalidArgumentException()); 17 | $this->assertPromiseRejectsWith($deferred->promise(), \LogicException::class); 18 | } catch (Exception $exception) { 19 | $this->assertMatchesRegularExpression('/Failed asserting that promise rejects with a specified reason/', $exception->getMessage()); 20 | $this->assertMatchesRegularExpression( 21 | '/Failed asserting that LogicException Object .+ is an instance of class "InvalidArgumentException"/', 22 | $exception->getMessage() 23 | ); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/WaitForPromiseToFulfillTest.php: -------------------------------------------------------------------------------- 1 | reject(new Exception()); 18 | $this->waitForPromiseToFulfill($deferred->promise()); 19 | } catch (Exception $exception) { 20 | $this->assertMatchesRegularExpression( 21 | '/Failed to fulfill a promise. It was rejected with Exception/', 22 | $exception->getMessage() 23 | ); 24 | } 25 | } 26 | } 27 | --------------------------------------------------------------------------------