Testing doubles

Important : For your tests to work, your PhpUnit test case classes must extend the Doublit\TestCase class. Read more on the getting started page.

Counting method calls

To test how many times a double method is being called, use the count method. There are two ways :

Using comparators

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');

Using a callback

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);
});

Changing a method's behaviour

You can change the behaviour of a double method with the stub method.

Return a simple value

You can replace the return value of a method with a custom value :

// Make "myMethod" method return "hello"
$my_double::_method('myMethod')->stub('hello');

Return a custom value

You can also use the stubs methods gathered in the Doublit\Stubs class. They give a little bit more options. Here are some examples on how to use them :

use Doublit\Stubs;
... 

// Make "myMethod" method return its second argument
$my_double::_method('myMethod')->stub(Stubs::returnArgument(2));

// Make "myMethod" method return the result of the "MyClass::myMethod" method
$my_double::_method('myMethod')->stub(Stubs::callback('MyClass::myMethod'));

// Make "myMethod" method throw a "MyException" exception with message "my message" (optional)
$my_double::_method('myMethod')->stub(Stubs::throwException(MyException::class, 'my message'));

// Make "myMethod" method return the instance of class double
$my_double::_method('myMethod')->stub(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')->stub(Stubs::returnValueMap([['one', 'two'],['blue', 'yellow']], ['numbers', 'colors']);

Run a callback

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')->stub(function($arg1){
    if($arg1 == 'my_value'){
        return 'my_return';
    } else {
        return 'my_other_return';
    }
});

Specify call count overwriting

For each of the above ways of changing a method's behaviour, 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')->stub('hello', 2);

// Make "myOtherMethod" method return "hello" the second time and third time it is called
$my_double::_method('my_other_method')->stub('hello', [2,3]);  

Make method behave like a "mock"

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();

Make method behave like a "dummy"

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();

Testing method's arguments

Use the args method to test the value of arguments passed to a double method.

Against a simple value

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);

Against PhpUnit constraints

You can also use the PhpUnit constraints, gathered in the Doublit\Constraints class. These will give you more options to test your arguments :

use Doublit\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 $argument is true.
  • Constraints::isFalse() : Test that $argument is false.
  • Constraints::isNull() : Test that $argument is null.
  • Constraints::isNotNull() : Test that $argument is not null.
  • Constraints::isInfinite() : Test that $argument is infinite.
  • Constraints::isFinite() : Test that $argument is not infinite.
  • Constraints::isNan() : Test that $argument is not a number.
  • Constraints::isEmpty() : Test that $argument is empty.
  • Constraints::anything() : Test that $argument is anything.
  • Constraints::equalTo($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) : Test that $argument is equal to $value ($argument == $value)
  • Constraints::identicalTo($value) : Test that $argument is identical to $value ($argument === $value)
  • Constraints::isType($type) : Test that $argument is of type `$type.
  • Constraints::isInstanceOf($className) : Test that $argument is instance of `$className.
  • Constraints::stringStartsWith($prefix) : Test that string $argument starts with $prefix
  • Constraints::stringContains($string, $case = true) : Test that string $argument contains $string
  • Constraints::stringEndsWith($suffix) : Test that string $argument ends with $suffix
  • Constraints::matchesRegularExpression($pattern) : Test that string $argument matches $pattern regular expression.
  • Constraints::greaterThan($value) : Test that numerical $argument is greater than $value.
  • Constraints::greaterThanOrEqual($value) : Test that numerical $argument is greater or equal to $value.
  • Constraints::lessThan($value) : Test that numerical $argument is less than $value.
  • Constraints::lessThanOrEqual($value) : Test that numerical $argument is less or equal to $value.
  • Constraints::countOf($count) : Test that countable $argument has expected $count size.
  • Constraints::contains($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) : Test if array $argument contains $value.
  • Constraints::containsOnly($type) : Test that array $argument contains only variables of type $type.
  • Constraints::containsOnlyInstancesOf($classname) : Test that array $argument contains only instances of class $classname.
  • Constraints::arrayHasKey($key) : Test that array $argument has the $key.
  • Constraints::arraySubset($subset, $strict = false) : Test that array $argument contains the $subset.
  • Constraints::isWritable() : Test that $argument is writable.
  • Constraints::isReadable() : Test that $argument is readable.
  • Constraints::directoryExists() : Test that $argument is an existent directory.
  • Constraints::fileExists() : Test that $argument is an existent file.
  • Constraints::isJson(): Test that $argument is a json.
  • Constraints::jsonMatches($expectedJson) : Test that json $argument is identical to $expectedJson
  • Constraints::logicalAnd(...$args) : Test that $argument matches all $args constraints
  • Constraints::logicalOr(...$args) : Test that $argument matches any of the $args constraints
  • Constraints::logicalXor(...$args) : Test that $argument matches only one of $args constraints
  • Constraints::logicalNot(Constraint $constraint) : Test that $argument doesn't match the constraint $constraint
  • Constraints::callback($callback) : Test that $callback with give argument $argument returns true ($callback = function($argument){ return $argument == "test" };)
  • Constraints::attributeEqualTo($attributeName, $value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) : Test that class or object $argument has attribute $attributeName with value $value.
  • Constraints::attribute(Constraint $constraint, $attributeName) : Test classor object $argument has attribute $attributeName that matches given $constraint constraint.
  • Constraints::classHasAttribute($attributeName) : Test that class $argument has attribute $attributeName.
  • Constraints::classHasStaticAttribute($attributeName) : Test that class $argument has static attribute $attributeName.
  • Constraints::objectHasAttribute($attributeName) : Test that object $argument has attribute $attributeName.

Manualy

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');
    }
});

Specify call count test

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 everytime 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 :

// Test "myMethod"
$double::_method('myMethod')
    ->count('1') // make sure it is called exactly 1 time,
    ->stub('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 :

// Set my_param to true
$my_double->my_property = true;

Spies

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 = Doublit::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 = Doublit::dummy_instance(MyClass::class);

// Test method
$double::_method('myMethod')
    ->count(1)
    ->args(['arg1', 'arg2']);

// Run method "myMethod"
$double->myMethod('arg1','arg2');

Info : It is no possible to change a method behaviour with spies.