phpUnit — How to control output of multiple functions while writing a test of same class.
Let’s consider a simple functionality
How do we test if multiple functions are called inside a function and we want to mock their results as well? Well, go ahead and read it.
<?php
declare(strict_types=1);namespace Calculator;use Calculator\Operands;Class Calculator
{
public function __construct(Operands $operands)
{
$this->operand1 = $operands->getOperand(1);
$this->operand2 = $operands->getOperand(2);
} public function add()
{
return $this->operand1 + $this->operand2;
}
}
To test above functionality, we can create a MockClass of Operands::class and instruct phpUnit to return both the functions with arguments as follows.
<?php
declare(strict_types=1);namespace Calculator\Tests;use PHPUnit\Framework\TestCase;
use Calculator\Operands;
use Calculator\Calculator;class CalculatorTest extends TestCase
{ /**
* @dataProvider additionProvider
*/
public function testMultipleOperandsAreSupported($op1, $op2, $result): void
{
$operands = $this->createMock(Operands::class);
$operands
->expects($this->any())
->method('getOperand')
->withConsecutive([1], [2])
->willReturnOnConsecutiveCalls($op1, $op2); $calc = new Calculator($operands);
$calc->assertEquals($calc->add(), $result);
} // return multiple sets of operands and results
public function additionProvider(): array
{
return [
[0, 0, 0],
[1, 2, 3],
[-1, 1, 0],
// ...more cases
];
}
}
Breakdown of Above Code(which is useful)
By above example, we learn a few thing for the MockClass Object($operands). Let’s break it down
expects($this->any())
checks if the following function is called once, twice or as many times, alternates are$this->once()
,$this->at(:int)
method('getOperand')
checks which method will be called while execution; in this case ‘getOperand’.withConsecutive([1],[2])
checks for calls to the methods with given parameters, In this case, ‘getOperand’ is called with parameter (1) first and (2) again, consecutively.
Note: The above parameters are in array, so if we had 2 parameters while calling getOperand
it would be like withConsecutive([1,true],[2,false])
i.e. array of the parameters.
willReturnOnConsecutiveCalls($op1, $op2)
mocks the response of the calls made above with that set of parameters.
What we learned and what can be achieved
We learned how we can control multiple calls to a method of a mock object and choose what should be each of their response based on the argument.
We can achieve even more through making the argument of the method a dynamic argument and control the output to write a functional test.
Hope you learned something.