gstarczyk / mimic
Stubbing and mocking tool for PHP loosely based on Java's Mockito
Requires
- php: ^7.2
Requires (Dev)
- phpunit/phpunit: ^8.0
This package is auto-updated.
Last update: 2024-10-14 15:21:21 UTC
README
Simple tool to create mocks for classes.
It is developed under influence of great java tool Mockito.
Table of Contents
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 passedwithAnyArguments()
- when you don't care about argumentswith(....)
- 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 methodwillReturnCallbackResult(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 "==" operatorMatch::same($value)
- compare using "===" operatorMatch::anyString()
Match::anyInteger()
Match::anyFloat()
Match::anyTraversable()
- match arrays and objects implements \TraversableMatch::anyObject($className = null)
Match::stringStartsWith($prefix)
Match::stringEndsWith($prefix)
available invocation count verifiers
Times::exactly($value)
Times::atLeast($value)
Times::once()
- same asTimes::exactly(1)
Times::never()
- same asTimes::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());