gstarczyk/mimic

Stubbing and mocking tool for PHP loosely based on Java's Mockito

2.0.2 2020-03-29 06:42 UTC

README

Simple tool to create mocks for classes.

It is developed under influence of great java tool Mockito.

Table of Contents

  1. Installation
  2. Requirements
  3. Usage
  4. Examples

Installation

To install just add 'gstarczyk/mimic' to your composer.json

composer require --dev gstarczyk/mimic

Requirements

Mimic requires PHP in at least 5.5 version.

Usage

creation of mock

$mock = Gstarczyk\Mimic\Mimic::mock(SomeClass::class); or just $mock = Mimic::mock(SomeClass::class); if you import Mimic class.

And that's it, you just create mock;

You can use InitMock helper which makes creation of mocks more convenient.

Define test case class properties that will have @var and @mock annotations then run Mimic::initMocks($testCase) before each test.

For any defined and properly marked property will generated mock and assigned to property.

You can also define property for tested object and mark it with @var and @injectMocks annotations. Tested object will be created with mocks injected into constructor. Class constructor for Tested object must require only objects. These required objects should be defined as mocks.

class MyService
{
}

class MyOtherService
{
}

class MyServiceToTest
{
    /** @var MyService */
    private $service;

    /** @var MyOtherService */
    private $otherService;

    public function __construct(MyService $service, MyOtherService $otherService)
    {
        $this->service = $service;
        $this->otherService = $otherService;
    }
}

class TestCase extends \SomeFameousPhpTestFramework_TestCase
{
    /**
     * @var \MyNameSpace\MyServiceToTest
     * @injectMocks
     */
    private $testedObject;

    /**
     * @var \MyNameSpace\MyService
     * @mock
     */
    private $mock1;

    /**
     * @var \MyNameSpace\MyOtherService
     * @mock
     */
    private $mock2;

    protected function setUp()
    {
        Mimic::initMocks($this);
    }
}

Unfortunately due to nature of PHP annotations you have to use full className in "var" annotations or provide path to test case file (to scan imports).

class TestCase extends \SomeFameousPhpTestFramework_TestCase
{
    /**
     * @var MyServiceToTest
     * @injectMocks
     */
    private $testedObject;

    /**
     * @var MyService
     * @mock
     */
    private $mock1;

    /**
     * @var MyOtherService
     * @mock
     */
    private $mock2;

    protected function setUp()
    {
        Mimic::initMocks($this, __FILE__);
    }
}

define stub

To define stub (behavior of mocked object) use Mimic::when() method:

$mock = Mimic::mock(SomeClass::class);
Mimic::when($mock)
    ->invoke('someMethod') // choose method
    ->withoutArguments()   // choose arguments
    ->willReturn('some value'); // define behaviour

To choose arguments you can use three methods:

  • withoutArguments() - when no arguments should be passed
  • withAnyArguments() - when you don't care about arguments
  • with(....) - when you want specify for what arguments you define behaviour,
                       equal() value matcher will be used to check arguments. 
                       You can refine matching by using value matchers in arguments list
                       (see examples).
    

To define behaviour of stub use methods listed below:

  • willReturn(mixed $value) - to specify value returned by method
  • willReturnCallbackResult(Closure $callback) - to dynamically prepare returned value. Arguments of invoked method are passed to $callback.
  • willThrow(Exception $exception) - to throw exception during invocation

You can define different action for each sets of arguments:

Mimic::when($mock)
    ->invoke('someMethod')
    ->with(10, 20)
    ->willReturn('some value');
    
Mimic::when($mock)
    ->invoke('someMethod')
    ->with(100, 200)
    ->willReturnCallbackResult(function($arg1, $arg2) {
        return $arg1 + $arg2;
    });

You can refine arguments list by using value matchers:

Mimic::when($mock)
    ->invoke('someMethod')
    ->with(Match::anyInteger(), 20)
    ->willReturn('some value');

In case you want stub consecutive invocations you can use specialized stub builder:

Mimic::when($mock)
    ->consecutiveInvoke('someMethod')
    ->willReturn('first')
    ->thenReturn('second')
    ->thenReturn('third');

Using this builder you cannot define arguments but only consecutive behaviours (silently withAnyArguments() is used).

verification of method invocations

To verify that some method of your mocked object was invoked use Mimic::verify() method.

Mimic::verify($mock)
    ->method('someMethod')
    ->with(100, 200)
    ->wasCalled(Times::exactly(1));

Again, you can refine arguments list by using value matchers:

Mimic::verify($mock)
    ->method('someMethod')
    ->with(Match::anyInteger(), 200)
    ->wasCalled(Times::exactly(1));

You also can verify consecutive invocations:

Mimic::verify($mock)
    ->consecutiveMethodInvocations('getMore')
    ->wasCalledWith('a')
    ->thenWith('b')
    ->thenWithAnyArguments()
    ->thenWith('c')
    ->thenWithoutArguments();

or

Mimic::verify($mock)
    ->consecutiveMethodInvocations('getMore')
    ->wasCalledWithoutArguments()
    ...
        

or

Mimic::verify($mock)
    ->consecutiveMethodInvocations('getMore')
    ->wasCalledWithAnyArguments()
    ...

available value matchers

  • Match::equal($value) - compare using "==" operator
  • Match::same($value) - compare using "===" operator
  • Match::anyString()
  • Match::anyInteger()
  • Match::anyFloat()
  • Match::anyTraversable() - match arrays and objects implements \Traversable
  • Match::anyObject($className = null)
  • Match::stringStartsWith($prefix)
  • Match::stringEndsWith($prefix)

available invocation count verifiers

  • Times::exactly($value)
  • Times::atLeast($value)
  • Times::once() - same as Times::exactly(1)
  • Times::never() - same as Times::exactly(0)

arguments capturing

Sometimes you need do more fancy assertions on verified method's arguments. You could use arguments captor, and use your favorite assertion tool on captured data.

$mock->someMethod('a');
$mock->someMethod('b');
$mock->someMethod('c', 'd');

$captor = new ArgumentsCaptor();
Mimic::verify($mock)
        ->method('someMethod')
        ->with($captor)
        ->wasCalled(Times::exactly(3));

$capturedData = $captor->getValues();
$expectedData = [
    ['a'],
    ['b'],
    ['c', 'd']
];
Assert::assertEquals($expectedData, $capturedData);

spy a method

Sometime you need to the original method of the mock to be call.

Mimic::spy($mock, 'someMethod');
$mock->someMethod('a', 'b');
// The method 'someMethod' of the original class will be call,
// The invocation is still be counted

examples

Let's assume that we have classes:

class Person
{
    private $birthDate;

    public function __construct(\DateTimeImmutable $birthDate)
    {
        $this->birthDate = $birthDate;
    }

    public function getAge()
    {
        $now = new \DateTimeImmutable('now');
        $age = $now->diff($this->birthDate);

        return $age->y;
    }
}

class AgeVerifier
{
    /**
     * @param Person $customer
     * @return bool
     */
    public function isAdult(Person $customer)
    {
        return $customer->getAge() >= 18;
    }
}

and we want test AgeVerifier::isAdult() method, but without running code in Person::getAge().

To do such "fancy" thing we need to:

$person = Mimic::mock(Person::class);
$verifier = new AgeVerifier();
Mimic::when($person)
    ->invoke('getAge')
    ->withoutArguments()
    ->willReturn(20);
$result = $verifier->isAdult($person);

assert($result === true);

and if we want verify that AgeVerifier use Person::getAge:

$person = Mimic::mock(Person::class);
$verifier = new AgeVerifier();
$verifier->isAdult($person);

Mimic::verify($person)
    ->method('getAge')
    ->withoutArguments()
    ->wasCalled(Times::once());