Highly opinionated mocking framework for PHP 5.3+

Related tags

Testing prophecy
Overview

Prophecy

Stable release Build Status

Prophecy is a highly opinionated yet very powerful and flexible PHP object mocking framework. Though initially it was created to fulfil phpspec2 needs, it is flexible enough to be used inside any testing framework out there with minimal effort.

A simple example

<?php

class UserTest extends PHPUnit\Framework\TestCase
{
    private $prophet;

    public function testPasswordHashing()
    {
        $hasher = $this->prophet->prophesize('App\Security\Hasher');
        $user   = new App\Entity\User($hasher->reveal());

        $hasher->generateHash($user, 'qwerty')->willReturn('hashed_pass');

        $user->setPassword('qwerty');

        $this->assertEquals('hashed_pass', $user->getPassword());
    }

    protected function setUp()
    {
        $this->prophet = new \Prophecy\Prophet;
    }

    protected function tearDown()
    {
        $this->prophet->checkPredictions();
    }
}

Installation

Prerequisites

Prophecy requires PHP 7.2.0 or greater.

Setup through composer

First, add Prophecy to the list of dependencies inside your composer.json:

{
    "require-dev": {
        "phpspec/prophecy": "~1.0"
    }
}

Then simply install it with composer:

$> composer install --prefer-dist

You can read more about Composer on its official webpage.

How to use it

First of all, in Prophecy every word has a logical meaning, even the name of the library itself (Prophecy). When you start feeling that, you'll become very fluid with this tool.

For example, Prophecy has been named that way because it concentrates on describing the future behavior of objects with very limited knowledge about them. But as with any other prophecy, those object prophecies can't create themselves - there should be a Prophet:

$prophet = new Prophecy\Prophet;

The Prophet creates prophecies by prophesizing them:

$prophecy = $prophet->prophesize();

The result of the prophesize() method call is a new object of class ObjectProphecy. Yes, that's your specific object prophecy, which describes how your object would behave in the near future. But first, you need to specify which object you're talking about, right?

$prophecy->willExtend('stdClass');
$prophecy->willImplement('SessionHandlerInterface');

There are 2 interesting calls - willExtend and willImplement. The first one tells object prophecy that our object should extend a specific class. The second one says that it should implement some interface. Obviously, objects in PHP can implement multiple interfaces, but extend only one parent class.

Dummies

Ok, now we have our object prophecy. What can we do with it? First of all, we can get our object dummy by revealing its prophecy:

$dummy = $prophecy->reveal();

The $dummy variable now holds a special dummy object. Dummy objects are objects that extend and/or implement preset classes/interfaces by overriding all their public methods. The key point about dummies is that they do not hold any logic - they just do nothing. Any method of the dummy will always return null and the dummy will never throw any exceptions. Dummy is your friend if you don't care about the actual behavior of this double and just need a token object to satisfy a method typehint.

You need to understand one thing - a dummy is not a prophecy. Your object prophecy is still assigned to $prophecy variable and in order to manipulate with your expectations, you should work with it. $dummy is a dummy - a simple php object that tries to fulfil your prophecy.

Stubs

Ok, now we know how to create basic prophecies and reveal dummies from them. That's awesome if we don't care about our doubles (objects that reflect originals) interactions. If we do, we need to use stubs or mocks.

A stub is an object double, which doesn't have any expectations about the object behavior, but when put in specific environment, behaves in specific way. Ok, I know, it's cryptic, but bear with me for a minute. Simply put, a stub is a dummy, which depending on the called method signature does different things (has logic). To create stubs in Prophecy:

$prophecy->read('123')->willReturn('value');

Oh wow. We've just made an arbitrary call on the object prophecy? Yes, we did. And this call returned us a new object instance of class MethodProphecy. Yep, that's a specific method with arguments prophecy. Method prophecies give you the ability to create method promises or predictions. We'll talk about method predictions later in the Mocks section.

Promises

Promises are logical blocks, that represent your fictional methods in prophecy terms and they are handled by the MethodProphecy::will(PromiseInterface $promise) method. As a matter of fact, the call that we made earlier (willReturn('value')) is a simple shortcut to:

$prophecy->read('123')->will(new Prophecy\Promise\ReturnPromise(array('value')));

This promise will cause any call to our double's read() method with exactly one argument - '123' to always return 'value'. But that's only for this promise, there's plenty others you can use:

  • ReturnPromise or ->willReturn(1) - returns a value from a method call
  • ReturnArgumentPromise or ->willReturnArgument($index) - returns the nth method argument from call
  • ThrowPromise or ->willThrow($exception) - causes the method to throw specific exception
  • CallbackPromise or ->will($callback) - gives you a quick way to define your own custom logic

Keep in mind, that you can always add even more promises by implementing Prophecy\Promise\PromiseInterface.

Method prophecies idempotency

Prophecy enforces same method prophecies and, as a consequence, same promises and predictions for the same method calls with the same arguments. This means:

$methodProphecy1 = $prophecy->read('123');
$methodProphecy2 = $prophecy->read('123');
$methodProphecy3 = $prophecy->read('321');

$methodProphecy1 === $methodProphecy2;
$methodProphecy1 !== $methodProphecy3;

That's interesting, right? Now you might ask me how would you define more complex behaviors where some method call changes behavior of others. In PHPUnit or Mockery you do that by predicting how many times your method will be called. In Prophecy, you'll use promises for that:

$user->getName()->willReturn(null);

// For PHP 5.4
$user->setName('everzet')->will(function () {
    $this->getName()->willReturn('everzet');
});

// For PHP 5.3
$user->setName('everzet')->will(function ($args, $user) {
    $user->getName()->willReturn('everzet');
});

// Or
$user->setName('everzet')->will(function ($args) use ($user) {
    $user->getName()->willReturn('everzet');
});

And now it doesn't matter how many times or in which order your methods are called. What matters is their behaviors and how well you faked it.

Note: If the method is called several times, you can use the following syntax to return different values for each call:

$prophecy->read('123')->willReturn(1, 2, 3);

This feature is actually not recommended for most cases. Relying on the order of calls for the same arguments tends to make test fragile, as adding one more call can break everything.

Arguments wildcarding

The previous example is awesome (at least I hope it is for you), but that's not optimal enough. We hardcoded 'everzet' in our expectation. Isn't there a better way? In fact there is, but it involves understanding what this 'everzet' actually is.

You see, even if method arguments used during method prophecy creation look like simple method arguments, in reality they are not. They are argument token wildcards. As a matter of fact, ->setName('everzet') looks like a simple call just because Prophecy automatically transforms it under the hood into:

$user->setName(new Prophecy\Argument\Token\ExactValueToken('everzet'));

Those argument tokens are simple PHP classes, that implement Prophecy\Argument\Token\TokenInterface and tell Prophecy how to compare real arguments with your expectations. And yes, those classnames are damn big. That's why there's a shortcut class Prophecy\Argument, which you can use to create tokens like that:

use Prophecy\Argument;

$user->setName(Argument::exact('everzet'));

ExactValueToken is not very useful in our case as it forced us to hardcode the username. That's why Prophecy comes bundled with a bunch of other tokens:

  • IdenticalValueToken or Argument::is($value) - checks that the argument is identical to a specific value
  • ExactValueToken or Argument::exact($value) - checks that the argument matches a specific value
  • TypeToken or Argument::type($typeOrClass) - checks that the argument matches a specific type or classname
  • ObjectStateToken or Argument::which($method, $value) - checks that the argument method returns a specific value
  • CallbackToken or Argument::that(callback) - checks that the argument matches a custom callback
  • AnyValueToken or Argument::any() - matches any argument
  • AnyValuesToken or Argument::cetera() - matches any arguments to the rest of the signature
  • StringContainsToken or Argument::containingString($value) - checks that the argument contains a specific string value
  • InArrayToken or Argument::in($array) - checks if value is in array
  • NotInArrayToken or Argument::notIn($array) - checks if value is not in array

And you can add even more by implementing TokenInterface with your own custom classes.

So, let's refactor our initial {set,get}Name() logic with argument tokens:

use Prophecy\Argument;

$user->getName()->willReturn(null);

// For PHP 5.4
$user->setName(Argument::type('string'))->will(function ($args) {
    $this->getName()->willReturn($args[0]);
});

// For PHP 5.3
$user->setName(Argument::type('string'))->will(function ($args, $user) {
    $user->getName()->willReturn($args[0]);
});

// Or
$user->setName(Argument::type('string'))->will(function ($args) use ($user) {
    $user->getName()->willReturn($args[0]);
});

That's it. Now our {set,get}Name() prophecy will work with any string argument provided to it. We've just described how our stub object should behave, even though the original object could have no behavior whatsoever.

One last bit about arguments now. You might ask, what happens in case of:

use Prophecy\Argument;

$user->getName()->willReturn(null);

// For PHP 5.4
$user->setName(Argument::type('string'))->will(function ($args) {
    $this->getName()->willReturn($args[0]);
});

// For PHP 5.3
$user->setName(Argument::type('string'))->will(function ($args, $user) {
    $user->getName()->willReturn($args[0]);
});

// Or
$user->setName(Argument::type('string'))->will(function ($args) use ($user) {
    $user->getName()->willReturn($args[0]);
});

$user->setName(Argument::any())->will(function () {
});

Nothing. Your stub will continue behaving the way it did before. That's because of how arguments wildcarding works. Every argument token type has a different score level, which wildcard then uses to calculate the final arguments match score and use the method prophecy promise that has the highest score. In this case, Argument::type() in case of success scores 5 and Argument::any() scores 3. So the type token wins, as does the first setName() method prophecy and its promise. The simple rule of thumb - more precise token always wins.

Getting stub objects

Ok, now we know how to define our prophecy method promises, let's get our stub from it:

$stub = $prophecy->reveal();

As you might see, the only difference between how we get dummies and stubs is that with stubs we describe every object conversation instead of just agreeing with null returns (object being dummy). As a matter of fact, after you define your first promise (method call), Prophecy will force you to define all the communications - it throws the UnexpectedCallException for any call you didn't describe with object prophecy before calling it on a stub.

Mocks

Now we know how to define doubles without behavior (dummies) and doubles with behavior, but no expectations (stubs). What's left is doubles for which we have some expectations. These are called mocks and in Prophecy they look almost exactly the same as stubs, except that they define predictions instead of promises on method prophecies:

$entityManager->flush()->shouldBeCalled();

Predictions

The shouldBeCalled() method here assigns CallPrediction to our method prophecy. Predictions are a delayed behavior check for your prophecies. You see, during the entire lifetime of your doubles, Prophecy records every single call you're making against it inside your code. After that, Prophecy can use this collected information to check if it matches defined predictions. You can assign predictions to method prophecies using the MethodProphecy::should(PredictionInterface $prediction) method. As a matter of fact, the shouldBeCalled() method we used earlier is just a shortcut to:

$entityManager->flush()->should(new Prophecy\Prediction\CallPrediction());

It checks if your method of interest (that matches both the method name and the arguments wildcard) was called 1 or more times. If the prediction failed then it throws an exception. When does this check happen? Whenever you call checkPredictions() on the main Prophet object:

$prophet->checkPredictions();

In PHPUnit, you would want to put this call into the tearDown() method. If no predictions are defined, it would do nothing. So it won't harm to call it after every test.

There are plenty more predictions you can play with:

  • CallPrediction or shouldBeCalled() - checks that the method has been called 1 or more times
  • NoCallsPrediction or shouldNotBeCalled() - checks that the method has not been called
  • CallTimesPrediction or shouldBeCalledTimes($count) - checks that the method has been called $count times
  • CallbackPrediction or should($callback) - checks the method against your own custom callback

Of course, you can always create your own custom prediction any time by implementing PredictionInterface.

Spies

The last bit of awesomeness in Prophecy is out-of-the-box spies support. As I said in the previous section, Prophecy records every call made during the double's entire lifetime. This means you don't need to record predictions in order to check them. You can also do it manually by using the MethodProphecy::shouldHave(PredictionInterface $prediction) method:

$em = $prophet->prophesize('Doctrine\ORM\EntityManager');

$controller->createUser($em->reveal());

$em->flush()->shouldHaveBeenCalled();

Such manipulation with doubles is called spying. And with Prophecy it just works.

Comments
  • Partial Mocks

    Partial Mocks

    Hi,

    I know that the purpose of this library is mainly for PHPSpec and therefore tries to satifsy another purpose. I still use it in regular tests, because the API is so easy to use, especially compared to PHPUnit mocks. Unfortunately there is one downside: Prophecy is not able to create partial mocks. Sometimes I want to test a method that gets Data from the same object but from another method. And I don't want to set up all data for the other method, because I want to unit test only one. In such a scenario, I would need to mock only one method, which I understand is currently not possible neither intended to be possible in prophecy.

    I just think that this tool is to useful to limit it just for PHPSpec. My question therefore is, if it would be possible to implement partial mocks in the future?

    opened by KeKs0r 38
  • Add PHP 7.1 support

    Add PHP 7.1 support

    resolves: https://github.com/phpspec/prophecy/issues/280

    • Allows void return type
    • Allows iterable type
    • Allows nullable arguments
    • Allows nullable return types
    opened by prolic 33
  • Introduced and fixed CachedDoubler behaviour in order to reduce memor…

    Introduced and fixed CachedDoubler behaviour in order to reduce memor…

    …y usage

    With this PR we resolve https://github.com/phpspec/prophecy/issues/96 and https://github.com/phpspec/phpspec/issues/884.

    Our empirical tests:

    ~ 6000 PHPSpec examples

    Before patch:

    • 1GB 900MB memory consumption
    • 31676 declared classes
    • 105 seconds per suite execution

    After patch:

    • 146MB (-1.301 %)
    • 3148 declared classes (-1.006 %)
    • 9 seconds per suite execution (- 1.166 %)

    Only thing that should be considered here is the static-ness nature of CachedDoubler. Is this fine or is it improvable?

    opened by DonCallisto 28
  • Mock magic methods

    Mock magic methods

    Hi there.

    Sorry to open this issue, seems that it was already discussed, here, here and here.

    But, right now there is no solution to mock object that didn't define public API using PHP implementations. And sadly, many libraries uses magic calls, just talk about the example : AWS. I Guess that I can find other popular examples.

    AWS use PHPDoc to describe API when doing magic functions, see PhpDoc @method.

    So, what about using this information to read public API ? PHPStorm use this information to build auto-completion.

    Regards,

    Feature request 
    opened by armetiz 27
  • add support for 'self' and 'parent' return type

    add support for 'self' and 'parent' return type

    When a php7 return type is self, like so:

    class Foo {
      public function setBar(Bar $bar) : self
      {
          ...
          return $this;
      }
    }
    

    Extending Foo::setBar like so

    class Baz extends Foo {
      public function setBar(Bar $bar) : self
      {
          ...
      }
    }
    

    will result in

    Compile Error: Declaration of Baz::setBar(Bar $bar): Baz must be compatible with Foo::setBar(Bar $bar): Foo
    

    So the extending class must respect the return type:

    class Baz extends Foo {
      public function setBar(Bar $bar) : Foo
      {
          ...
      }
    }
    
    opened by bendavies 26
  • Partial Mocking

    Partial Mocking

    Hi guys, There is any news about Partial Mocking supported in v1 ? Here is an example:

    function it_will_call_b_and_delegate_to_a() {
      $this->a('foo', 'bar')->shouldBeCalled();
      $this->b('bar');
    }
    
    public function a($foo, $bar) {
      // do a
    }
    
    public function b($bar) {
      // do something to create foo
      $this->a($foo, $bar);
    }
    

    With partial mocks will be possible to mocking $this public methods.

    opened by hlegius 25
  • Problem while doubling MongoDB ODM QueryBuilder

    Problem while doubling MongoDB ODM QueryBuilder

    Hello,

    I'm having trouble mocking a Doctrine\ODM\MongoDB\Query\Builder object. In fact, this class uses an optionnal dependency "GeoJson" to perform geo-related operation.

    Problem appends when this package is not installed, prophecy will generate a non PHP standard compliant class (because one of the QB method is typehinted with a GeoJson class).

    You can see the problem happening here: https://travis-ci.org/akeneo/pim-community-dev/jobs/21412381#L510

    I'm not really convinced this is a prophecy issue, maybe it's more a mongodb-odm issue that should not bring hard-coupling between an optionnal dependency.

    Anyway, I've found an easy fix in https://github.com/phpspec/prophecy/blob/master/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php#L77 which consits in always prepending \\ to the argument's class, but I'm not sure I totally get the purpose of this line 77.

    Could you enlight me, please?

    Regards

    opened by gquemener 21
  • Implement Prophet::getNumberOfPredictions()

    Implement Prophet::getNumberOfPredictions()

    For better integration with PHPUnit, for instance, it would be nice if a Prophet::getNumberOfPredictions() method existed that returns the number of predictions (expectations in PHPUnit terminilogy).

    In https://github.com/sebastianbergmann/phpunit/commit/6c21c66567031c863f207fb46b3c1c545d5589d1 I added the following workaround for now:

    foreach ($this->prophet->getProphecies() as $objectProphecy) {
        foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) {
            foreach ($methodProphecies as $methodProphecy) {
                if ($methodProphecy->getPrediction() !== null) {
                    $this->numAssertions++;
                }
            }
        }
    }
    

    The above seems to work but this functionality should be part of Prophecy. This was already discussed in https://github.com/phpspec/prophecy-phpunit/pull/6 and https://github.com/phpspec/prophecy-phpunit/issues/13.

    opened by sebastianbergmann 20
  • 1.10.0 regression

    1.10.0 regression

    With the release of 1.10.0 we noticed some of our tests breaking. After some digging this is because the behavior has slightly changed. Maybe it's best explained with an example:

    require __DIR__ . '/vendor/autoload.php';
    
    class A {
        public function someMethod() {
            return 42;
        }
        public function otherMethod() {
        }
    }
    
    class Regression {
        public function test(A $a) {
            try {
                $a->someMethod();
            } catch (\Throwable $e) {
                return false;
            }
            return true;
        }
    }
    
    $prophet = new Prophecy\Prophet();
    $prophecy = $prophet->prophesize(A::class);
    $prophecy->otherMethod()->shouldNotBeCalled();
    
    var_dump((new Regression())->test($prophecy->reveal()));
    

    With 1.9.0 this outputs: bool(false) With 1.10.0 this outputs: bool(true)

    This is because the prophecy no longer throws an error when dealing with methods which have not been prophesied. And while I admit this is a poor test, it means that some of our tests are breaking and need fixing with a feature release.

    Is this an intended side effect?

    opened by yannickl88 17
  • Mocking magic methods

    Mocking magic methods

    I'm trying to mock magic methods, and it's impossible. Specically __get and __set. I've written some work arounds to the problem, but the tests are unwieldy.

    Is there any simple way that I can mock ArrayAccess or Magic Methods?

    opened by CMCDragonkai 17
  • Trait method not being found?

    Trait method not being found?

    I have a trait on a class which shouldBeCalled how ever this prophecy is fine but when the tested code runs it says it doesn't exist on the object.

    My trait

    trait AppliesEvents
    {
      public function apply(AggregateChanged $event);
    }
    

    My class (gets mocked)

    class Deal
    {
      use AppliesEvents;
    }
    

    My test code:

        public function it_will_apply_unlist_deal_event_on_deal($deals, UnlistDealCommand $command, Deal $deal)
        {
            $deals->findByKey('123')->willReturn($deal);
            $command->dealId()->willReturn('123');
            $deal->apply(Argument::type(UnlistDeal::class))->shouldBeCalled();
    
            $this->handle($command);
        }
    

    The code being tested:

        /**
         * @param UnlistDealCommand $command
         */
        public function handle(UnlistDealCommand $command)
        {
            $deal = $this->deals->findByKey($command->dealId());
    
            if (is_null($deal)) {
                throw new \RuntimeException('Unable to locate deal.');
            }
    
            $deal->apply(new UnlistDeal);
    
            $this->deals->persist($deal);
        }
    

    If I var_dump($deal) I can see $methodProphecies contaons apply but when I call it I get

    it will apply unlist deal event on deal
    method `Double\Deal\P4::apply()` not found.
    

    Any ideas?

    opened by bweston92 16
  • Object of class Exception could not be converted to int

    Object of class Exception could not be converted to int

    $ ./bin/phpunit --filter ExampleTest
    PHPUnit 9.5.27 by Sebastian Bergmann and contributors.
    
    E                                                                   1 / 1 (100%)
    
    Time: 00:00.023, Memory: 30.00 MB
    
    There was 1 error:
    
    1) CoreTest\ExampleTest::testShouldRegisterException
    ErrorException: Notice: Object of class Exception could not be converted to int
    
    tests/CoreTest/ExampleTest.php:26
    

    Example of testcase:

    <?php
    
    namespace CoreTest;
    
    use PHPUnit\Framework\TestCase;
    use Prophecy\Argument;
    use Prophecy\PhpUnit\ProphecyTrait;
    use Psr\Log\LoggerInterface;
    
    class ExampleTest extends TestCase
    {
        use ProphecyTrait;
    
        public function testShouldRegisterException()
        {
            $logger = $this->prophesize(LoggerInterface::class);
    
            $logger->error(Argument::type('string'), Argument::allOf(
                Argument::withEntry('id', 10),
                Argument::withEntry('exception', new \Exception('wrong')),
            ))
                ->shouldBeCalled();
    
            $logger->reveal()->error('message', [
                'id' => 10,
                'exception' => new \Exception('wrong'),
                'other' => rand(0, 1000),
            ]);
        }
    }
    

    The error is thrown because the ExactValueToken checks if the value or argument maybe a object, and if only one of them is, it checks for a __toString in the object.

    But PHP does not convert the object to string, before comparing it to a int|float.

    When one of them (value or argument) is a object and the other is a int|float it needs to cast the object to string before comparing OR return false because they do not have the same type.

    I already opened a PR to fix it: #583

    opened by lucassabreu 1
  • Add the ability to customize the __toString representation of a CallbackToken

    Add the ability to customize the __toString representation of a CallbackToken

    Whenever we run tests using CallbackToken's and the CallbackToken fails to match, we get unhelpful assertion failures. When the specific test case does not use a lot of CallbackToken's, it is pretty easy to track which assertion has failed. This is aimed at the scenario where there are multiple identical function signatures with different CallbackToken's, so the following example becomes more readable:

        [exec]  Test  tests/unit/Example/ExampleTest.php:testItDoesSomething
         [exec] Expected exactly 1 calls that match:
         [exec]   Double\ExampleInterface\P2->doSomething(callback())
         [exec] but none were made.
         [exec] Recorded `doSomething(...)` calls:
    

    should become

        [exec]  Test  tests/unit/Example/ExampleTest.php:testItDoesSomething
         [exec] Expected exactly 1 calls that match:
         [exec]   Double\ExampleInterface\P2->doSomething(Clear description)
         [exec] but none were made.
         [exec] Recorded `doSomething(...)` calls:
    

    If you would like a separate issue for discussion, please let me know so I can create one.

    opened by ian-zunderdorp 0
  • Method with a class argument and default value can't be mocked

    Method with a class argument and default value can't be mocked

    Hi, since 8.1, PHP supports the usage of the new operator in initializers (https://php.watch/rfcs/new_in_initializers).

    Given this dummy class:

    class DummyClass {}
    

    And an interface with a method using a DummyClass instance as default argument of a method:

    interface BarService
    {
        public function doIt(DummyClass $myClass = new DummyClass()): void;
    }
    

    And a service using the interface as a dependency:

    class FooService
    {
        public function __construct(
            private readonly BarService $bar
        ) {
        }
    
        public function execute(): void
        {
            $this->bar->doIt();
        }
    }
    

    The following test fails with a fatal PHP error in the phpspec/prophecy code:

    namespace Tests;
    
    use PHPUnit\Framework\TestCase;
    use Prophecy\PhpUnit\ProphecyTrait;
    
    class DummyClass {...}
    interface BarService {...}
    class FooService {...}
    
    class FooServiceTest extends TestCase
    {
        use ProphecyTrait;
    
        /**
         * @test
         */
        public function it_fails(): void
        {
            $someService = $this->prophesize(BarService::class);
            $someService
                ->doIt()
                ->shouldBeCalled();
    
            $fooService = new FooService($someService->reveal());
            $fooService->execute();
        }
    }
    

    The error message is:

    PHP Fatal error:  Constant expression contains invalid operations in /vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php(49) : eval()'d code on line 5
    
    PHP8.1 
    opened by bicpi 0
  • Method with an enum argument and default value can't be mocked

    Method with an enum argument and default value can't be mocked

    Hi, given this enum (PHP 8.1+):

    enum AppType
    {
        case IOS;
        case ANDROID;
    }
    

    And an interface with a method using the enum as default argument of a method:

    interface BarService
    {
        public function doIt(AppType $appType = AppType::ANDROID): void;
    }
    

    And a service using the interface as a dependency:

    class FooService
    {
        public function __construct(
            readonly private BarService $bar
        ) {
        }
    
        public function execute(): void
        {
            $this->bar->doIt();
        }
    }
    

    The following test fails:

    namespace Tests;
    
    use PHPUnit\Framework\TestCase;
    use Prophecy\PhpUnit\ProphecyTrait;
    
    enum AppType {...}
    interface BarService {...}
    class FooService {...}
    
    class FooServiceTest extends TestCase
    {
        use ProphecyTrait;
    
        /**
         * @test
         */
        public function it_fails(): void
        {
            $someService = $this->prophesize(BarService::class);
            $someService
                ->doIt()
                ->shouldBeCalled();
    
            $fooService = new FooService($someService->reveal());
            $fooService->execute();
        }
    }
    

    The error message is:

    Error : Class "Double\BarService\Tests\AppType" not found
    

    When passing the default value explicitly works though.

    PHP8.1 
    opened by bicpi 5
Releases(v1.16.0)
  • v1.16.0(Nov 29, 2022)

    • [added] Allow install with PHP 8.2 [@gquemener]
    • [added] Use shorter object IDs for object comparison [@TysonAndre]
    • [added] Support standalone false,true and null types [@kschatzle]
    • [added] Support doubling readonly classes [@gquemener]
    • [fixed] Remove workarounds for unsupported HHVM [@TysonAndre]
    • [fixed] Clear error message when doubling DNF types [@kschatzle]
    Source code(tar.gz)
    Source code(zip)
  • v1.15.0(Dec 8, 2021)

    • [added] Support for the static return type [@denis-rolling-scopes]
    • [fixed] Add return types for Comparator implementations to avoid deprecation warnings from Symfony's DebugClassLoader [@stof]
    Source code(tar.gz)
    Source code(zip)
  • 1.14.0(Sep 10, 2021)

    1.14.0 / 2021/09/16

    • [added] Support for static closures in will and should [@ntzm]
    • [added] Allow install on PHP 8.1 (with test suite fixes) [@javer]
    • [added] Support for the 'never' return type [@ciaranmcnulty]
    • [fixed] Better error message when doubling intersection return types [@ciaranmcnulty]
    Source code(tar.gz)
    Source code(zip)
  • 1.13.0(Mar 17, 2021)

    • [added] willYield can now specify a return value [@camilledejoye]
    • [added] Prophecy exception interfaces are explicitly Throwable [@ciaranmcnulty]
    • [fixed] Argument::in() and notIn() now marked as static [@tyteen4a03]
    • [fixed] Can now double unions containing false [@ciaranmcnulty]
    • [fixed] Virtual magic methods with arguments are now doublable in PHP 8 [@ciaranmcnulty]
    Source code(tar.gz)
    Source code(zip)
  • 1.12.2(Dec 19, 2020)

  • 1.12.1(Sep 29, 2020)

  • 1.12.0(Sep 28, 2020)

    • [added] PHP 8 support [@ciaranmcnulty]
    • [added] Argument::in() and Argument::notIn() [@viniciusalonso]
    • [added] Support for union and mixed types [@ciaranmcnulty]
    • [fixed] Issues caused by introduction of named parameters [@ciaranmcnulty]
    • [fixed] Issues caused by stricter rounding [@ciaranmcnulty]
    Source code(tar.gz)
    Source code(zip)
  • 1.11.1(Jul 8, 2020)

  • 1.11.0(Jul 7, 2020)

    • [changed] dropped support for PHP versions earlier than 7.2 (@ciaranmcnulty)
    • [fixed] removed use of Reflection APIs deprecated in PHP 8.0 (@Ayesh)
    Source code(tar.gz)
    Source code(zip)
  • v1.10.3(Mar 5, 2020)

  • v1.10.2(Jan 20, 2020)

  • 1.10.1(Dec 22, 2019)

  • 1.10.0(Dec 17, 2019)

    • [added] shouldHaveBeenCalled evaluation happens later so un-stubbed calls don't throw (@elvetemedve)
    • [added] methods can now be doubled case-insensitively to match PHP semantics (@michalbundyra)
    • [fixed] reduced memory usage by optimising CachedDoubler (@DonCallisto)
    • [fixed] removed fatal error nesting level when comparing large objects (@scroach)
    Source code(tar.gz)
    Source code(zip)
  • 1.9.0(Oct 3, 2019)

    • [added] Add willYield feature to Method Prophecy(@tkotosz)
    • [fixed] Allow MethodProphecy::willThrow() to accept Throwable as string (@timoschinkel )
    • [fixed] Allow new version of phpdocumentor/reflection-docblock (@ricpelo)
    Source code(tar.gz)
    Source code(zip)
  • 1.8.1(Jun 13, 2019)

  • 1.8.0(Aug 5, 2018)

    • Support for void return types without explicit will (@crellbar)
    • Clearer error message for unexpected method calls (@meridius)
    • Clearer error message for aggregate exceptions (@meridius)
    • More verbose shouldBeCalledOnce expectation (@olvlvl)
    • Ability to double Throwable, or methods that extend it (@ciaranmcnulty)
    • [fixed] Doubling methods where class has additional arguments to interface (@webimpress)
    • [fixed] Doubling methods where arguments are nullable but default is not null (@webimpress)
    • [fixed] Doubling magic methods on parent class (@dsnopek)
    • [fixed] Check method predictions only once (@dontub)
    • [fixed] Argument::containingString throwing error when called with non-string (@dcabrejas)
    Source code(tar.gz)
    Source code(zip)
  • 1.7.6(Apr 18, 2018)

  • 1.7.5(Feb 19, 2018)

  • 1.7.4(Feb 11, 2018)

  • 1.7.3(Nov 24, 2017)

  • v1.7.2(Sep 5, 2017)

  • v1.7.1(Sep 3, 2017)

    • Allow PHP5 keywords methods generation on PHP7 (thanks @bycosta)
    • Allow reflection-docblock v4 (thanks @GrahamCampbell)
    • Check method predictions only once (thanks @dontub)
    • Escape file path sent to \SplFileObjectConstructor when running on Windows (thanks @danmartin-epiphany)
    Source code(tar.gz)
    Source code(zip)
  • v1.7.0(Mar 2, 2017)

    • Add full PHP 7.1 Support (thanks @prolic)
    • Allow sebastian/comparator ^2.0 (thanks @sebastianbergmann)
    • Allow sebastian/recursion-context ^3.0 (thanks @sebastianbergmann)
    • Allow \Error instances in ThrowPromise (thanks @jameshalsall)
    • Support phpspec/phpspect ^3.2 (thanks @Sam-Burns)
    • Fix failing builds (thanks @Sam-Burns)
    Source code(tar.gz)
    Source code(zip)
  • v1.6.2(Nov 21, 2016)

    • Added support for detecting @method on interfaces that the class itself implements, or when the stubbed class is an interface itself (thanks @Seldaek)
    • Added support for sebastian/recursion-context 2 (thanks @sebastianbergmann)
    • Added testing on PHP 7.1 on Travis (thanks @danizord)
    • Fixed the usage of the phpunit comparator (thanks @Anyqax)
    Source code(tar.gz)
    Source code(zip)
  • v1.6.1(Jun 7, 2016)

    • Ignored empty method names in invalid @method phpdoc
    • Fixed the mocking of SplFileObject
    • Added compatibility with phpdocumentor/reflection-docblock 3
    Source code(tar.gz)
    Source code(zip)
  • v1.6.0(Feb 15, 2016)

    • Add Variadics support (thanks @pamil)
    • Add ProphecyComparator for comparing objects that need revealing (thanks @jon-acker)
    • Add ApproximateValueToken (thanks @dantleech)
    • Add support for 'self' and 'parent' return type (thanks @bendavies)
    • Add __invoke to allowed reflectable methods list (thanks @ftrrtf)
    • Updated ExportUtil to reflect the latest changes by Sebastian (thanks @jakari)
    • Specify the required php version for composer (thanks @jakzal)
    • Exclude 'args' in the generated backtrace (thanks @oradwell)
    • Fix code generation for scalar parameters (thanks @trowski)
    • Fix missing sprintf in InvalidArgumentException __construct call (thanks @emmanuelballery)
    • Fix phpdoc for magic methods (thanks @Tobion)
    • Fix PhpDoc for interfaces usage (thanks @ImmRanneft)
    • Prevent final methods from being manually extended (thanks @kamioftea)
    • Enhance exception for invalid argument to ThrowPromise (thanks @Tobion)
    Source code(tar.gz)
    Source code(zip)
  • v1.5.0(Aug 13, 2015)

    • Add support for PHP7 scalar type hints (thanks @trowski)
    • Add support for PHP7 return types (thanks @trowski)
    • Update internal test suite to support PHP7
    Source code(tar.gz)
    Source code(zip)
  • v1.4.1(Apr 27, 2015)

  • v1.4.0(Mar 27, 2015)

    • Fixed errors in return type phpdocs (thanks @sobit)
    • Fixed stringifying of hash containing one value (thanks @avant1)
    • Improved clarity of method call expectation exception (thanks @dantleech)
    • Add ability to specify which argument is returned in willReturnArgument (thanks @coderbyheart)
    • Add more information to MethodNotFound exceptions (thanks @ciaranmcnulty)
    • Support for mocking classes with methods that return references (thanks @edsonmedina)
    • Improved object comparison (thanks @whatthejeff)
    • Adopted '^' in composer dependencies (thanks @GrahamCampbell)
    • Fixed non-typehinted arguments being treated as optional (thanks @whatthejeff)
    • Magic methods are now filtered for keywords (thanks @seagoj)
    • More readable errors for failure when expecting single calls (thanks @dantleech)
    Source code(tar.gz)
    Source code(zip)
  • v1.1.2(Jan 24, 2014)

Owner
PHPSpec Framework
PHPSpec Framework
PHP Mocking Framework

Phake Phake is a framework for PHP that aims to provide mock objects, test doubles and method stubs. Phake was inspired by a lack of flexibility and e

Phake 469 Dec 2, 2022
The most powerful and flexible mocking framework for PHPUnit / Codeception.

AspectMock AspectMock is not an ordinary PHP mocking framework. With the power of Aspect Oriented programming and the awesome Go-AOP library, AspectMo

Codeception Testing Framework 777 Dec 12, 2022
A PHP library for mocking date and time in tests

ClockMock Slope s.r.l. ClockMock provides a way for mocking the current timestamp used by PHP for \DateTime(Immutable) objects and date/time related f

Slope 44 Dec 7, 2022
Add mocking capabilities to Pest or PHPUnit

This repository contains the Pest Plugin Mock. The Mocking API can be used in regular PHPUnit projects. For that, you just have to run the following c

PEST 16 Dec 3, 2022
Removes final keywords from source code on-the-fly and allows mocking of final methods and classes

Removes final keywords from source code on-the-fly and allows mocking of final methods and classes. It can be used together with any test tool such as PHPUnit or Mockery.

David Grudl 326 Dec 9, 2022
The modern, simple and intuitive PHP unit testing framework.

atoum PHP version atoum version 5.3 -> 5.6 1.x -> 3.x 7.2 -> 8.x 4.x (current) A simple, modern and intuitive unit testing framework for PHP! Just lik

atoum 1.4k Nov 29, 2022
Full-stack testing PHP framework

Codeception Modern PHP Testing for everyone Codeception is a modern full-stack testing framework for PHP. Inspired by BDD, it provides an absolutely n

Codeception Testing Framework 4.6k Jan 7, 2023
AST based PHP Mutation Testing Framework

Infection - Mutation Testing framework Please read documentation here: infection.github.io Twitter: @infection_php Discord: https://discord.gg/ZUmyHTJ

Infection - Mutation Testing Framework for PHP 1.8k Jan 2, 2023
:heavy_check_mark: PHP Test Framework for Freedom, Truth, and Justice

Kahlan is a full-featured Unit & BDD test framework a la RSpec/JSpec which uses a describe-it syntax and moves testing in PHP one step forward. Kahlan

Kahlan 1.1k Jan 2, 2023
Event driven BDD test framework for PHP

The highly extensible, highly enjoyable, PHP testing framework. Read more at peridot-php.github.io or head over to the wiki. Building PHAR Peridot's p

Peridot 327 Jan 5, 2023
BDD test framework for PHP

BDD test framework for PHP, inspired by Jasmine and RSpec. Features a familiar syntax, and a watch command to automatically re-run specs during develo

Daniel St. Jules 286 Nov 12, 2022
SpecBDD Framework for PHP

phpspec The main website with documentation is at http://www.phpspec.net. Installing Dependencies Dependencies are handled via composer: wget -nc http

PHPSpec Framework 1.8k Dec 30, 2022
The PHP Unit Testing framework.

PHPUnit PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. Installat

Sebastian Bergmann 18.8k Jan 4, 2023
PHP unit testing framework with built in mocks and stubs. Runs in the browser, or via the command line.

Enhance PHP A unit testing framework with mocks and stubs. Built for PHP, in PHP! Quick Start: Just add EnhanceTestFramework.php and you are ready to

Enhance PHP 67 Sep 12, 2022
Pest is an elegant PHP Testing Framework with a focus on simplicity

Pest is an elegant PHP Testing Framework with a focus on simplicity. It was carefully crafted to bring the joy of testing to PHP. Explore the docs: pe

PEST 5.9k Dec 27, 2022
SimpleTest is a framework for unit testing, web site testing and mock objects for PHP

SimpleTest SimpleTest is a framework for unit testing, web site testing and mock objects for PHP. Installation Downloads All downloads are stored on G

SimpleTest 147 Jun 20, 2022
Humbug - a Mutation Testing framework for PHP

Humbug is a Mutation Testing framework for PHP to measure the real effectiveness of your test suites and assist in their improvement. It eats Code Coverage for breakfast.

Humbug 1.1k Dec 28, 2022
Essence is a very flexible BDD style assertion framework for PHP that fits into existing PHPUnit projects nicely

Essence 1.5.1 Essence is a very flexible BDD style assertion framework for PHP that fits into existing PHPUnit projects nicely. Installation composer

bound1ess 2 Apr 7, 2015