{.note .important} Important : For your tests to work, your PhpUnit test case classes must extend the
Doubles\TestCaseclass. Read more on the Quick start page.
To test how many times a double method is being called, use the count method. There are two ways :
Here are a few examples of what you can do using simple comparators :
// Test that the method "myMethod" is never called (0 times)
$double::_method('myMethod')->count(0);
// Test that the method "myMethod" is called exactly 2 times
$double::_method('myMethod')->count(2);
// Test that the method "myMethod" is called a least one time (more than 0 times)
$double::_method('myMethod')->count('>0');
// Test that the method "myMethod" is called a least one time (1 or more times)
$double::_method('myMethod')->count('>=1');
// Test that the method "myMethod" is called a less than 2 times
$double::_method('myMethod')->count('<2');
// Test that the method "myMethod" is called a less than 2 times (1 time or less)
$double::_method('myMethod')->count('<=1');
// Test that the method "myMethod" is called between 3 and 5 times
$double::_method('myMethod')->count('3-5');If you need more flexibility, you can also use a callback function to test the call count of a method. In the first
parameter of your callback function, you will be given a $call array containing all method calls with their arguments.
Here is and example testing that the myCallMethod is called 2 times through the __call magic method :
// Test that the "__call" method has receive "myCallMethod" 2 times as a first argument
$double::_method('__call')->count(function($calls){
$my_call_method_count = 0;
foreach($calls as $call){
$first_argument = $call['args'][0];
if(first_argument == 'myCallMethod'){
$my_call_method_count++;
}
}
$this->assertEqual($my_call_method_count, 2);
});You can change the behaviour of a double method with the stub method.
You can replace the return value of a method with a custom value :
// Make "myMethod" method return "hello"
$my_double::_method('myMethod')->return('hello');You can also use the stubs methods gathered in the Doubles\Stubs class. They give a little bit more options. Here are
some examples on how to use them :
use Doubles\Stubs;
...
// Make "myMethod" method return its second argument
$my_double::_method('myMethod')->return(Stubs::returnArgument(2));
// Make "myMethod" method return the result of the "MyClass::myMethod" method
$my_double::_method('myMethod')->return(Stubs::callback('MyClass::myMethod'));
// Make "myMethod" method throw a "MyException" exception with message "my message" (optional)
$my_double::_method('myMethod')->return(Stubs::throwException(MyException::class, 'my message'));
// Make "myMethod" method return the instance of class double
$my_double::_method('myMethod')->return(Stubs::returnSelf());
// Make "myMethod" method return "numbers" when arguments "one" and "two" are passed,
// and return "color" when arguments "blue" and "yellow" are passed
$my_double::_method('myMethod')->return(Stubs::returnValueMap([['one', 'two'],['blue', 'yellow']], ['numbers', 'colors']);If you want to have full control on the behaviour of a double method, you may also define your own callback to replace it :
// Make "myMethod" return "my_return" when first argument is "my_value" and return "my_other_return" otherwise
$my_double::_method('myMethod')->return(function($arg1){
if($arg1 == 'my_value'){
return 'my_return';
} else {
return 'my_other_return';
}
});For each of the above ways of modifying a method's return value, you can specify which calls is concerned by the change:
// Make "myMethod" method return "hello" the second time it is called
$my_double::_method('my_method')->return('hello', 2);
// Make "myOtherMethod" method return "hello" the second time and third time it is called
$my_double::_method('my_other_method')->return('hello', [2,3]);If you have a "mock" double and you still want some method to behave as in the original class, you should use the mock
method :
// Make "MyMethod" method behave as in the original class
$my_dummy_double::_method('myMethod')->mock();
// Make "MyMethod" and "myOtherMethod" methods behave as in the original class
$my_dummy_double::_method(['myMethod', 'myOtherMethod'])->mock();In the same way, if you have a "mock" double and you still want some method to behave like "dummy" and return null,
you should use the dummy method :
// Make "myMethod" method return "null" at call 2 and 3
$my_mock_double::_method('myMethod')->dummy([2,3]);
// Make "MyMethod" and "myOtherMethod" methods return "null"
$my_mock_double::_method(['myMethod', 'myOtherMethod'])->dummy();Use the args method to test the value of arguments passed to a double method.
You can test a method's arguments values with an array of values that the arguments should match. You can use null to
test that no arguments are passed to your method :
// Test the first argument passed to method "myMethod" is "value1" and the second "value2"
$double::_method('myMethod')->args(['value1', 'value2']);
// Test that no arguments are passed to method "myOtherMethod"
$double::_method('myOtherMethod')->args(null);You can also use the PhpUnit constraints, gathered in the Doubles\Constraints class. These will give you more options
to test your arguments :
use Doubles\Constraints;
// Test that the first argument passed to method "myMethod" an array and the second arguments an instance of class "MyClass"
$double::_method('myMethod')->args([Constraints::isType('array'), Constraints::isInstanceOf(MyClass::class)]);Here is a list of all available constraints to test against any argument $argument :
Constraints::isTrue(): Test that$argumentistrue.Constraints::isFalse(): Test that$argumentisfalse.Constraints::isNull(): Test that$argumentisnull.Constraints::isNotNull(): Test that$argumentis notnull.Constraints::isInfinite(): Test that$argumentis infinite.Constraints::isFinite(): Test that$argumentis not infinite.Constraints::isNan(): Test that$argumentis not a number.Constraints::isEmpty(): Test that$argumentis empty.Constraints::anything(): Test that$argumentis anything.Constraints::equalTo($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false): Test that$argumentis equal to$value($argument==$value)Constraints::identicalTo($value): Test that$argumentis identical to $value ($argument===$value)Constraints::isType($type): Test that$argumentis of type `$type.Constraints::isInstanceOf($className): Test that$argumentis instance of `$className.Constraints::stringStartsWith($prefix): Test that string$argumentstarts with$prefixConstraints::stringContains($string, $case = true): Test that string$argumentcontains$stringConstraints::stringEndsWith($suffix): Test that string$argumentends with$suffixConstraints::matchesRegularExpression($pattern): Test that string$argumentmatches$patternregular expression.Constraints::greaterThan($value): Test that numerical$argumentis greater than$value.Constraints::greaterThanOrEqual($value): Test that numerical$argumentis greater or equal to$value.Constraints::lessThan($value): Test that numerical$argumentis less than$value.Constraints::lessThanOrEqual($value): Test that numerical$argumentis less or equal to$value.Constraints::countOf($count): Test that countable$argumenthas expected$count size.Constraints::contains($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false): Test if array$argumentcontains$value.Constraints::containsOnly($type): Test that array$argumentcontains only variables of type$type.Constraints::containsOnlyInstancesOf($classname): Test that array$argumentcontains only instances of class$classname.Constraints::arrayHasKey($key): Test that array$argumenthas the$key.Constraints::arraySubset($subset, $strict = false): Test that array$argumentcontains the$subset.Constraints::isWritable(): Test that$argumentis writable.Constraints::isReadable(): Test that$argumentis readable.Constraints::directoryExists(): Test that$argumentis an existent directory.Constraints::fileExists(): Test that$argumentis an existent file.Constraints::isJson(): Test that$argumentis a json.Constraints::jsonMatches($expectedJson): Test that json$argumentis identical to$expectedJsonConstraints::logicalAnd(...$args): Test that$argumentmatches all$argsconstraintsConstraints::logicalOr(...$args): Test that$argumentmatches any of the$argsconstraintsConstraints::logicalXor(...$args): Test that$argumentmatches only one of$argsconstraintsConstraints::logicalNot(Constraint $constraint): Test that$argumentdoesn't match the constraint$constraintConstraints::callback($callback): Test that $callback with give argument$argumentreturnstrue($callback = function($argument){ return $argument == "test" };)Constraints::attributeEqualTo($attributeName, $value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false): Test that class or object$argumenthas attribute$attributeNamewith value$value.Constraints::attribute(Constraint $constraint, $attributeName): Test classor object$argumenthas attribute$attributeNamethat matches given$constraintconstraint.Constraints::classHasAttribute($attributeName): Test that class$argumenthas attribute$attributeName.Constraints::classHasStaticAttribute($attributeName): Test that class$argumenthas static attribute$attributeName.Constraints::objectHasAttribute($attributeName): Test that object$argumenthas attribute$attributeName.
If you need full control to test a method's arguments, you can run your own PhpUnit assertions using a callback function. You will be given all the arguments passed to your method :
// Test that the second argument passed to method "myMethod" is "value2" when the first argument's value is "value1"
$double::_method('myMethod')->args(function($arg1, $arg2){
if($arg1 == 'value1'){
$this->assertEqual($arg2, 'value2');
}
});If you don't specify on which method call you would like to test your arguments, they will be tested with the rules you
have specified every time the method is being called. You can specify on which method to test your arguments in the 2nd
argument of the args method :
// Test that arguments "value1" and "value2" were passed on the 3rd call of method "myMethod"
$double::_method('myMethod')->args(['value1', 'value2'], 3);
// Test that arguments "value1" and "value2" were passed on the 2nd and 3rd call of method "myMethod"
$double::_method('myMethod')->args(['value1', 'value2'], [2,3]);
``
## Putting it all together
You can chain your test assertions :
```php
// Test "myMethod"
$double::_method('myMethod')
->count('1') // make sure it is called exactly 1 time,
->return('my_return') // replace the return value by "my_return",
->args(['value1', 'value2'], 3); // and test its arguments are "value1" and "value2" on the third call
``
## Overwriting public properties
Only public class properties can be manipulated. To modify the value of a public property, just set its value like this :
```php
// Set my_param to true
$my_double->my_property = true;Somethings you may wish to run your double code first and test it afterwards. That's what we call spy tests. For that, you only need to write your method's tests after your double code.
To make a spy test, you would write your test in that order :
// Create class double
$double = Double::dummy_instance(MyClass::class);
// Run method "myMethod"
$double->myMethod('arg1','arg2');
// Test method
$double::_method('myMethod')
->count(1)
->args(['arg1', 'arg2']);Instead of writing it in that order :
// Create class double
$double = Double::dummy_instance(MyClass::class);
// Test method
$double::_method('myMethod')
->count(1)
->args(['arg1', 'arg2']);
// Run method "myMethod"
$double->myMethod('arg1','arg2');{.note .info} Info : It is no possible to change a method behaviour with spies.