Using PHPUnit, is there any way to mock a method and make it behave like identity (x->x) ? Something around the lines of :
$this->myMethod(Argument::any())->willReturn(<should return the argument untouched>)
You can use the returnArgument() method. From the doc:
Sometimes you want to return one of the arguments of a method call
(unchanged) as the result of a stubbed method call. Example 9.4 shows
how you can achieve this using returnArgument() instead of
returnValue().
As Example:
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnArgumentStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// Configure the stub.
$stub->method('doSomething')
->will($this->returnArgument(0));
// $stub->doSomething('foo') returns 'foo'
$this->assertEquals('foo', $stub->doSomething('foo'));
// $stub->doSomething('bar') returns 'bar'
$this->assertEquals('bar', $stub->doSomething('bar'));
}
}
Related
I use simple-phpunit from phpunit/phpunit-bridge by and with Symfony here.
I have a class A, that I desire to test, which uses a class B method in it.
While mocking the B and it's method I realise it is called multiple time and with multiple parameters : a class name and a integer.
I use returnValueMap() for this but have a problem : PHPUNIT seem to require that I precisely set the parameters, to their exact value when the method shall be called.
I can set the class name ( there are a given possible number fo them , so I can set them manually) but the given integer is arbitrary ( and the method is called in a loop ) , therefore I cannot set it precisely.
Is it possible to tell PHPUNIT to accept any parameter to this function ?
Like a anyParameter() method ( that I did not find after scouring the TestCase file ).
If I set the Integer parameter to a fixed value in the test and the class method, the test pass ( but of course I can't set the Integer as a fixed value in the class method ).
class ToBeTested{
public $objectB;
public function functionToTest($dataList){
......
$objectB=new ObjectB();
foreach($dataList as $data){
$className=$data['className'];
$integerNeeded=$data['integer'];
//Here if $inetegerNeeded is not the exact value set in the Mock
//$result will be null
$result=$this->objectB->doSomething($className,$integerNeeded);
if($result == null) throw new \Excpetion(" Object is null");
}
.....
}
}
class UnitTest extends TestCase{
/**
$generatedDataList comes from a DataProvider
with some varied and exotic data close to real use cases
**/
public function testFunction($generatedDataList)
{
$mockOfObjectB= $this->getMockBuilder(ObjectB::class)
->getMock();
/**
Here I'd like to use $this->anyParameter() or something that'd work that way
If I set this to a fixed value here and in the functionToTest() method it passes
**/
$anyInteger=-1;
$dataReferenceMap= [
['ClassNameA',$anyInteger,new MockObject()],
['ClassNameB',$anyInteger,new MockObject()],
['ClassNameC',$anyInteger,new MockObject()],
];
$mockOfObjectB
->expects($this->any())
->method('doSomething')
->will($this->returnValueMap($dataReferenceMap));
$objectToTest = new ToBeTested();
$objectToTest->objectB=$mockOfObjectB;
//DO THE TESTING - will because of the parameter Fail
$objectToTest->functionToTest($generatedDataList);
}
}
}
Might I also be wrongly returnValueMap() ?
It's quite new to me and I think the docs on it are pretty scarce and don't explain really well how to use it.
I would go with willReturnCallback. Here is the example (checked with phpunit 8.1)
<?php
declare(strict_types = 1);
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
public function testSome()
{
$mock = $this
->getMockBuilder(\stdClass::class)
->setMethods(['doSomething'])
->getMock();
$mock->method('doSomething')
->willReturnCallback(
function (string $classname, int $int) {
$map = [
'ClassNameA' => 123,
'ClassNameB' => 345,
// ...
];
return $map[$classname];
}
);
$this->assertSame(123, $mock->doSomething('ClassNameA', 1));
$this->assertSame(123, $mock->doSomething('ClassNameA', 1111111));
$this->assertSame(345, $mock->doSomething('ClassNameB', 13456));
$this->assertSame(345, $mock->doSomething('ClassNameB', 999999));
}
}
It is a shortcut for ->will($this->returnCallback(...)). The doc
I am trying to mock a mysqli object for my unit testing and therefore I have to either mock the property mysqli_result::__get() or mock the property mysqli_result::num_rows directly.
I already looked for a solution but the answers I found were just not working.
My Code looks like this:
use PHPUnit\Framework\TestCase;
class Mocks extends TestCase{
public function makeMysqliMock(array $queries): mysqli{
// build mocked mysqli-object
#$link = $this
# ->getMockBuilder("mysqli")
# // set methods
# ->setMethods(['__get', "query", "real_escape_string"])
# ->getMock();
$link = $this->createMock("mysqli");
// set method 'real_escape_string'
$link->expects($this->any())
->method("real_escape_string")
->will($this->returnCallback(function($str){
return addslashes($str);
}));
// set method 'query'
$link->expects($this->any())
->method("query")
->will($this->returnCallback(function(string $query) use ($queries){
// pick the query result for the current test
$data = isset($queries[$query]) ? $queries[$query] : null;
// build mocked 'mysqli_result'
if (is_array($data)){
$result = $this
->getMockBuilder("mysqli_result")
->setMethods(["fetch_assoc", "__get"])
->disableOriginalConstructor()
->getMock();
// build simulated fetch method
$result->expects($this->any())
->method("fetch_assoc")
->withAnyParameters()
->will($this->returnCallback(function() use ($data){
// set internal result pointer
static $index = 0;
// return current result - increment pointer
return isset($data[$index]) ? $data[$index++] : false;
}));
$result->expects($this->at(0))
->method("__get")
->with($this->equalTo("mysqli_num_rows"))
->will($this->returnValue(count($data)));
return $result;
}else {
return is_null($data) ? false : 1;
}
}));
return $link;
}
}
When I run that code phpunit gives me the following error message:
C:\xampp\htdocs\werimanage\php>php phpunit-6.0.11.phar tests
PHPUnit 6.0.11 by Sebastian Bergmann and contributors.
PHP Fatal error: Method Mock_mysqli_result_d3aa5482::__get() must take exactly 1 argument in phar://C:/xampp/htdocs/werimanage/php/phpunit-6.0.11.phar/phpunit-mock-objects/Generator.php(263) : eval()'d code on line 53
Fatal error: Method Mock_mysqli_result_d3aa5482::__get() must take exactly 1 argument in phar://C:/xampp/htdocs/werimanage/php/phpunit-6.0.11.phar/phpunit-mock-objects/Generator.php(263) : eval()'d code on line 53
So I do not know what the error is or how to fix it. I would really appreciate your help. Thanks!
according to the php documentation the method __get requires 1 argument (more over this is the error phpUnit is returning).
You can see on the documentation :
public mixed __get ( string $name )
The $name argument is the name of
the property being interacted with.
I imagine that you will be calling the __get method foreach of your column in your sql request. You then have to mock all the calls and the returns of the __get method with each argument (each column).
Hope this will help you.
This error happens because magic method __get must accept one parameter -- the name of the property. When PHPUnit generates the code to declare the mock it uses the declaration of the method from the original class. Since there is no method __get declared for mysqli_result PHPUnit mock generator declares it as __get() without parameters, and this is encountered as error.
If you really want to do your coding and testing the way you do it, you can use the following approach:
class SuperMock extends mysqli_result {
public function __get($name){
}
}
class Mocks extends PHPUnit_Framework_TestCase
{
public function testGet()
{
$data = ['one' => 1, 'two' => 2];
$mock = $this->getMockBuilder('SuperMock')
->disableOriginalConstructor()
->setMethods(['__get', 'fetch_assoc'])
->getMock();
$mock->expects($this->once())
->method('__get')
->with('nonexistent')
->willReturn(42);
$mock->expects($this->once())
->method('fetch_assoc')
->willReturn($data);
$this->assertInstanceOf('mysqli_result', $mock);
$this->assertSame(42, $mock->nonexistent);
$this->assertSame($data, $mock->fetch_assoc());
}
public function testDirectProperty(){
$mock = $this->getMockBuilder('SuperMock')
->disableOriginalConstructor()
->setMethods(['__get', 'fetch_assoc'])
->getMock();
$mock->nonexistent = 42;
$this->assertSame(42, $mock->nonexistent);
}
}
This will technically solve the issue. Still I would suggest revising your testing strategy. Because now it looks like you are testing how the interaction with a database is performed. But do you really need to test if data is fetched as an associative array, and that number of rows is figured out through mysqli_num_rows property? Going this way makes tests too much coupled to the production code. I believe this is the case when it is more suffucient to test the result being retrieved rather than the internal details of this process.
Suppose I have this class:
class SomeClass
{
// Top level function
public function execute($command)
{
// Get output from system tool
$output = $this->runTool($command);
// Check output for errors
if ($this->hasError($output))
return false;
// And parse success response from tool
return $this->parseOutput($output);
}
// There we're make a call to system
private function runTool($command)
{
return `/some/system/tool $command`;
}
[...]
}
I do not want to run system tool in my test, I want to replace a system call with predefined output.
So, the question is - should I create another class, move system call in it and mock that class in the test, or I can mock only that function of class which I will test?
Sure, both approaches will work, but which of them will be serve testing purposes better?
If you follow the single responsibility principle, you won't have this problem. Your class does not need to know how system calls are made, so you will have to use another class. You mock that.
IMO, in most cases when you need to mock protected or private methods, they do stuff that should be into another class and be mocked.
I would say it really depends on your infrastructure. Sometimes it is better to use Mock, sometimes Stub.
If the case is, that the class you want to test contains this unwanted method - use Mock and mock only this one function. That will make you sure, that any changes made to that class will be handled by the test.
If the unwanted function is a part of i.e. injected service or another class, which is not the domain of this particular test, you can create a stub.
You can't test private method, you can use a workaround and invoke it via reflection as described in this article and discussed in this SO QUESTION
But i suggest you to change the method visibility to protected and mock only the behaviour of the runTool method.
As example, suppose the following modified version of your class (i don't know how other method work so i suppose that you want to test their behaviour and take this implementation as example):
<?php
namespace Acme\DemoBundle\Service;
class SomeClass
{
// Top level function
public function execute($command)
{
// Get output from system tool
$output = $this->runTool($command);
// Check output for errors
if ($this->hasError($output))
return false;
// And parse success response from tool
return $this->parseOutput($output);
}
// There we're make a call to system
protected function runTool($command)
{
return `/some/system/tool $command`;
}
private function hasError($output)
{
return $output == "error";
}
private function parseOutput($output)
{
return json_decode($output);
}
}
As suppose the following test case:
<?php
namespace Acme\DemoBundle\Tests;
class SomeClassTest extends \PHPUnit_Framework_TestCase {
public function testCommandReturnError()
{
$mock = $this->getMockBuilder('Acme\DemoBundle\Service\SomeClass')
->setMethods(array('runTool'))
->disableOriginalConstructor()
->getMock()
;
$mock
->expects($this->exactly(1))
->method('runTool')
->with("commandName")
->will($this->returnValue("error"));
$this->assertFalse($mock->execute("commandName"));
}
public function testCommandReturnCorrectValue()
{
$mock = $this->getMockBuilder('Acme\DemoBundle\Service\SomeClass')
->setMethods(array('runTool'))
->disableOriginalConstructor()
->getMock()
;
$mock
->expects($this->exactly(1))
->method('runTool')
->with("commandName")
->will($this->returnValue('{"title":"myTitle"}'));
$returnValue = $mock->execute("commandName");
$this->assertEquals("myTitle", $returnValue->title);
}
}
Hope this help
I have a class that has two public method. It looks something like following:
class IntRequest
{
public function updateStatus()
{
$isValid = $this->checkValidity();
// ... next is a complex logic that use $isValid
}
/**
* #return bool
*/
public function isValid()
{
// another complex logic
}
}
I need to test a first function - IntRequesr::updateStatus; however I need to run to tests. The first one with IntRequests::isValid returns false and the second one with true as a result of IntRequests::isValid
I try to mock that function but tests run with calling actual IntRequests::isValid not mocked one.
My testing code is
$intRequest = new IntRequests;
$mock = m::mock($intRequest);
$mock->shouldReceive('isValid')
->once()
->andReturn(true);
$res = $mock->updateStatus();
$this->assertTrue($res);
I've try to call $res = $intRequest->updateStatus() instead of $res = $mock->updateStatus() but with no luck.
So, I am wondering is it possible to mock function that is called inside testing method?
You need a partial mock (a mock object, in which some of the methods are stubbed, while the rest are left as is). Since I've done such only with the phpunit's own mock library, I can only point you to the documentation, but it seems that you should just add ->makePartial() to your mock instantiation
I've got a problem with mocking an overloaded __get($index) method.
The code for the class to be mocked and the system under test that consumes it is as follows:
<?php
class ToBeMocked
{
protected $vars = array();
public function __get($index)
{
if (isset($this->vars[$index])) {
return $this->vars[$index];
} else {
return NULL;
}
}
}
class SUTclass
{
protected $mocky;
public function __construct(ToBeMocked $mocky)
{
$this->mocky = $mocky;
}
public function getSnack()
{
return $this->mocky->snack;
}
}
Test looks as follows:
<?php
class GetSnackTest extends PHPUnit_Framework_TestCase
{
protected $stub;
protected $sut;
public function setUp()
{
$mock = $this->getMockBuilder('ToBeMocked')
->setMethods(array('__get')
->getMock();
$sut = new SUTclass($mock);
}
/**
* #test
*/
public function shouldReturnSnickers()
{
$this->mock->expects($this->once())
->method('__get')
->will($this->returnValue('snickers');
$this->assertEquals('snickers', $this->sut->getSnack());
}
}
Real code is a little bit more complex, though not much, having "getSnacks()" in its parent class. But this example should suffice.
Problem is I get the following error, when executing the test with PHPUnit:
Fatal error: Method Mock_ToBeMocked_12345672f::__get() must take exactly 1 argument in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php(231)
When I debug I can't even reach the test method. It seems it breaks at setting up the mock object.
Any ideas?
__get() takes an argument, so you need to provide the mock with one:
/**
* #test
*/
public function shouldReturnSnickers()
{
$this->mock->expects($this->once())
->method('__get')
->with($this->equalTo('snack'))
->will($this->returnValue('snickers'));
$this->assertEquals('snickers', $this->sut->getSnack());
}
The with() method sets the argument for the mocked method in PHPUnit. You can find more details in the section on Test Doubles.
It's a bit hidden in the comments, but #dfmuir's answer put me on the right track. Mocking a __get method is straight forward if you use a callback.
$mock
->method('__get')
->willReturnCallback(function ($propertyName) {
switch($propertyName) {
case 'id':
return 123123123123;
case 'name':
return 'Bob';
case 'email':
return 'bob#bob.com';
}
}
);
$this->assertEquals('bob#bob.com', $mock->email);
Look in the mocked magic method __get. Probably you call there one more __get method from another and not properly mocked object.
What you are doing in the setUp method of your GetSnackTest class is incorrect.
If you want the code of the __get method to be executed (which would be the point of your test> I suppose), you have to change the way you call setMethods in the setup method.
Here's the complete explanation, but here's the relevant part:
Passing an array containing method names
The methods you have identified:
Are all stubs,
All return null by default,
Are easily overridable
So, you need to call setMethods by passing null, or by passing an array that contains some methods (the ones that you really want to stub), but not- __get (because you actually want the code of that method to be executed).
The, in the shouldReturnSnickers method, you will simply want to want to call $this->assertEquals('snickers', $this->sut->getSnack());, without the preceding lines with the expect part.
This will ensure the code of your __get method is actually executed and tested.
withAnyParameters() method can help you, this works correct:
$this->mock -> expects($this -> once())
-> method('__get') -> withAnyParameters()
-> will($this -> returnValue('snikers'));