phpUnit — How to control output of multiple functions while writing a test of same class.

Abhishek Jain
2 min readJul 19, 2021

--

Photo by Sebastian Herrmann on Unsplash

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.

--

--

No responses yet