PHPUnit cannot mock __get function on mock object - php

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.

Related

PHPUNIT Mocking method to accept any parameter

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

PHPUnit: Mock a method to behave like identity

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

Create a mock class whose methods return values when instantiated

How can I create a mock class (not just a mock object), with a method that, when instantiated will return a predictable value?
In the code below, I am testing a larger concept (accounts->preauthorize()), but I need to mock the object Lookup so that I can get predictable results for my test.
I'm using PHPUnit and CakePHP, if that matters. Here is my situation:
// The system under test
class Accounts
{
public function preauthorize()
{
$obj = new Lookup();
$result = $obj->get();
echo $result; // expect to see 'abc'
// more work done here
}
}
// The test file, ideas borrowed from question [13389449][1]
class AccountsTest
{
$foo = $this->getMockBuilder('nonexistent')
->setMockClassName('Lookup')
->setMethods(array('get'))
->getMock();
// There is now a mock Lookup class with the method get()
// However, when my code creates an instance of Lookup and calls get(),
// it returns NULL. It should return 'abc' instead.
// I expected this to make my instances return 'abc', but it doesn't.
$foo->expects($this->any())
->method('get')
->will($this->returnValue('abc'));
// Now run the test on Accounts->preauthorize()
}
You have several problems here, but the main one is that you are instantiating your Lookup class in the method that requires it. This makes it impossible to mock. You need to pass an instance of Lookup into this method to decouple the dependency.
class Accounts
{
public function preauthorize(Lookup $obj)
{
$result = $obj->get();
return $result; // You have to return something here, you can't test echo
}
}
Now you can mock Lookup.
class AccountsTest
{
$testLookup = $this->getMockBuilder('Lookup')
->getMock();
$testLookup->expects($this->any())
->method('get')
->will($this->returnValue('abc'));
$testAccounts = new Accounts();
$this->assertEquals($testAccounts->preauthorize($testLookup), 'abc');
}
Unfortunately, I can't test this test, but this should get you moving in the right direction.
Obviously, a unit test for the Lookup class should also exist.
You may also find my answer here of some use.

How to test Guzzle event in PHPUnit

I am having trouble understanding how I should go about unit testing a method which utilizes \Guzzle\Common\Event and has no return. I have a function
public function setRequest(\Guzzle\Common\Event $e) {
$e['request']->getQuery()->set('key', $this->getKey());
}
I cannot get the methods described at Guzzles mock object documentation to produce a successful test. What all needs to be mocked for this to work? getQuery() is part of the \Guzzle\Http\Message\Request I guess? What about the set()?
Edit: What I did so far is this, but I don't know if this is the correct approach. It succeeds, but it does not assert any test. I don't know that I can if the method is not returning anything.
public function testSetRequest()
{
$collection = $this->getMock('Guzzle\\Common\\Collection');
$collection->expects($this->once())
->method('set')
->will($this->returnValue(array('key' => 321)));
$request = $this->getMockBuilder('Guzzle\\Http\\Message\\Request')
->disableOriginalConstructor()
->getMock();
$request->expects($this->once())
->method('getQuery')
->will($this->returnValue($collection));
$event = $this->getMock('Guzzle\\Common\\Event');
$event->expects($this->once())
->method('offsetGet')
->with('request')
->will($this->returnValue($request));
$result = $this->place->setRequest($event);
}
I tracked set() down to the guzzle common collection. btw, $this->place simply refers to the instance of the object being tested, set in the setUp() function.

PHPUnit: Mocking __get() results in "__get() must take exactly 1 argument ..."

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

Categories