seregazhuk/php-react-promise-testing

View on GitHub
src/AssertsPromise.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace seregazhuk\React\PromiseTesting;

use Clue\React\Block;
use Exception;
use PHPUnit\Framework\AssertionFailedError;
use React\EventLoop\Factory as LoopFactory;
use React\EventLoop\LoopInterface;
use React\Promise\PromiseInterface;
use React\Promise\Timer\TimeoutException;

trait AssertsPromise
{
    protected int $defaultWaitTimeout = 2;

    private $loop;

    /**
     * @param PromiseInterface $promise
     * @param int|null $timeout seconds to wait for resolving
     * @throws AssertionFailedError
     */
    public function assertPromiseFulfills(PromiseInterface $promise, int $timeout = null): void
    {
        $failMessage = 'Failed asserting that promise fulfills. ';
        $this->addToAssertionCount(1);

        try {
            $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.');
        }
    }

    /**
     * @param PromiseInterface $promise
     * @param mixed $value
     * @param int|null $timeout
     * @throws AssertionFailedError
     */
    public function assertPromiseFulfillsWith(PromiseInterface $promise, $value, int $timeout = null): void
    {
        $failMessage = 'Failed asserting that promise fulfills with a specified value. ';
        $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->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
     * @throws AssertionFailedError
     */
    public function assertPromiseRejects(PromiseInterface $promise, int $timeout = null): void
    {
        $this->addToAssertionCount(1);

        try {
            $this->waitForPromise($promise, $timeout);
        } catch (Exception $exception) {
            return;
        }

        $this->fail('Failed asserting that promise rejects. Promise was fulfilled.');
    }

    /**
     * @throws AssertionFailedError
     */
    public function assertPromiseRejectsWith(PromiseInterface $promise, string $reasonExceptionClass, int $timeout = null): void
    {
        try {
            $this->waitForPromise($promise, $timeout);
        } catch (Exception $reason) {
            $this->assertInstanceOf(
                $reasonExceptionClass, $reason, 'Failed asserting that promise rejects with a specified reason.'
            );

            return;
        }

        $this->fail('Failed asserting that promise rejects. Promise was fulfilled.');
    }

    /**
     * @throws Exception
     * @return mixed
     */
    public function waitForPromiseToFulfill(PromiseInterface $promise, int $timeout = null)
    {
        try {
            return $this->waitForPromise($promise, $timeout);
        } catch (Exception $exception) {
            $reason = get_class($exception);
            $this->fail("Failed to fulfill a promise. It was rejected with {$reason}.");
        }
    }

    /**
     * @return mixed
     * @throws Exception
     */
    public function waitForPromise(PromiseInterface $promise, int $timeout = null)
    {
        return Block\await($promise, $this->eventLoop(), $timeout ?: $this->defaultWaitTimeout);
    }

    public function eventLoop(): LoopInterface
    {
        if (! $this->loop) {
            $this->loop = LoopFactory::create();
        }

        return $this->loop;
    }

    /**
     * @param PromiseInterface $promise
     * @param callable $predicate
     * @param int|null $timeout
     * @throws AssertionFailedError
     */
    public function assertTrueAboutPromise(
        PromiseInterface $promise,
        callable $predicate,
        int $timeout = null
    ): void {
        $this->assertAboutPromise($promise, $predicate, $timeout);
    }

    /**
     * @param PromiseInterface $promise
     * @param callable $predicate
     * @param int|null $timeout
     * @throws AssertionFailedError
     */
    public function assertFalseAboutPromise(
        PromiseInterface $promise,
        callable $predicate,
        int $timeout = null
    ): void {
        $this->assertAboutPromise($promise, $predicate, $timeout, false);
    }

    /**
     * @param PromiseInterface $promise
     * @param callable $predicate
     * @param int|null $timeout
     * @param bool $assertTrue
     * @throws AssertionFailedError
     */
    private function assertAboutPromise(
        PromiseInterface $promise,
        callable $predicate,
        int $timeout = null,
        bool $assertTrue = true
    ): void {
        $result = $assertTrue ? false : true;
        $this->addToAssertionCount(1);

        try {
            $result = $predicate($this->waitForPromise($promise, $timeout));
        } catch (TimeoutException $exception) {
            $this->fail('Promise was cancelled due to timeout');
        } catch (Exception $exception) {
            $this->fail('Failed asserting that promise was fulfilled. Promise was rejected');
        }

        $assertTrue ? $this->assertTrue($result) : $this->assertFalse($result);
    }
}