PHPunit mocking call mocked method in same mocked object - php

I have question about mocking object...
I have class "Example", and I need to test callMethod()
public function callMethod() {
$item = 0;
foreach($this->returnSomething() as $list) {
$item = $item + $list->sum;
}
return $item;
}
I have test method where I mock "returnSomething" to return me some data, but problem is than It doesn't call mocked method.
This is part of test method where im mocking "returnSomething" and call "callMethod".
$mock = mock("Example");
$mock->shouldReceive("returnSomething")->once()->withNoArgs()->andReturn($returnItems);
$result = $mock->callMethod();
Is it possible to call mocked "returnSomething" without changing "callMethod" definition and forwarding $mock object into that method?

I am writing this because today I found the answer here, but setMethods() is deprecated (phpunit 8.5), the alternative is onlyMethods() and it can be used as follows:
$mock = $this->getMockBuilder(Example::class)
->onlyMethods(['yourOnlyMethodYouWantToMock'])
->getMock();
$mock->method('yourOnlyMethodYouWantToMock')
->withAnyParameters()
->willReturn($yourReturnValue);

It is possible to mock only specified method.
Examples:
Mockery:
$mock = \Mockery::mock("Example[returnSomething]");
PHPUnit:
$mock = $this->getMock('Example', array('returnSomething'));
or
$mock = $this->getMockBuilder('Example')
->setMethods(array('returnSomething'))
->getMock();
With above cases a framework will mock only returnSomething method and leave the rest methods as in original object.

Related

Mockery not able to call my method in testing method

I'm trying to write a test for a method in the class below. However, when I run the test I get the error that get_b64 is never run? I don't see how this is not running.
I've had a little look into the mockery documentation for testing static methods, but as far as I can tell this error isn't due to that?
What do I need to change with my testing strategy or be able to mock the function call in the mocked object?
Class:
namespace App\Services\Steam;
use App\Services\Steam\Utils;
class Steam
{
public function profile(string $steamID)
{
$b64 = Utils::get_b64($steamID);
if ($b64 === null) {
throw new \App\Exceptions\InvalidSteamId();
}
return new Profile($b64);
}
}
TestCase:
public function test_create_user_object()
{
$id = "123"
$utilsMock = Mockery::mock(\App\Services\Steam\Utils::class);
$utilsMock->shouldReceive('get_b64')
->once()
->with($id)
->andReturn($id);
$steam = new \App\Services\Steam\Steam();
$steam->profile($id);
}
You call get_b64 statically, which means it is called from the class, not an object.
To mock such calls you need to use aliases:
public function test_create_user_object()
{
$id = "123"
$utilsMock = Mockery::mock('alias:\App\Services\Steam\Utils');
$utilsMock->shouldReceive('get_b64')
->once()
->with($id)
->andReturn($id);
$steam = new \App\Services\Steam\Steam();
$steam->profile($id);
}
Bear in mind that it completely replaces the Utils class, so if you have more static functions called from the class, you need to mock them as well.

PHPUnit cannot mock __get function on mock object

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.

Mocking Laravel Eloquent models - how to set a public property with Mockery

I want to use a mock object (Mockery) in my PHPUnit test. The mock object needs to have both some public methods and some public properties set. The class is a Laravel Eloquent model. I tried this:
$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true); //works fine
$mock->roles = 2; //how to do this? currently returns an error
$this->assertTrue(someTest($mock));
... but setting the public property returns this error:
BadMethodCallException: Method Mockery_0_User::setAttribute() does not exist on this mock object
This error is not returned when mocking a simple class, but is returned when I try to mock an Eloquent model. What am I doing wrong?
If you want getting this property with this value, just use it:
$mock->shouldReceive('getAttribute')
->with('role')
->andReturn(2);
If you call $user->role you will get - 2
($user - its User mock class)
This answer is a bit late but hopefully it will help someone. You can currently set a static property on mocked Eloquent objects by using the 'alias' keyword:
$mocked_model = Mockery::mock('alias:Namespace\For\Model');
$mocked_model->foo = 'bar';
$this->assertEquals('bar', $mocked_model->foo);
This is also helpful for mocking external vendor classes like some of the Stripe objects.
Read about 'alias' and 'overload' keywords:
http://docs.mockery.io/en/latest/reference/startup_methods.html
To answer your question, you could also try something like this:
$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true); //works fine
$mock->shouldReceive('setAttribute')->passthru();
$mock->roles = 2;
$mock->shouldReceive('getAttribute')->passthru();
$this->assertEquals(2, $mock->roles);
Or, as suggested by seblaze, use a partial mock:
$mock = Mockery::mock('User[hasRole]');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2;
$this->assertEquals(2, $mock->roles);
But, from your code snippet, if you're writing unit tests, you should really only make one assertion per each test:
function test_someFunctionWhichCallsHasRole_CallsHasRole() {
$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once();
$mock->someFunctionWhichCallsHasRole();
}
function test_someFunctionWhichCallsHasRole_hasRoleReturnsTrue_ReturnsTrue() {
$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$result = $mock->someFunctionWhichCallsHasRole();
$this->assertTrue($result);
}
Tried this? It should cover you issue.
https://github.com/padraic/mockery/blob/master/docs/11-MOCKING-PUBLIC-PROPERTIES.md
I'd say implement these
protected $roles = array();
public function setRoles($roles)
{
$this->roles = $roles;
}
public function addRole($role)
{
$this->roles[] = $role;
}
Then you can test using:
$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->addRole(2);
$this->assertTrue(someTest($mock));
This apse gives you the opportunity to promise a format when you do a getRoles() which would be array of Role object if you do SOLID OOP, or if you rather use array, then at least you know it's always an array you get.
Spy is your friend on this:
$mock = Mockery::spy('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2;
$this->assertTrue(someTest($mock));
Did you tried to do partial mocks ?
You can try something like ( NOT TESTED ) :
$mock = Mockery::mock('User[hasRole]');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2;
$this->assertTrue(someTest($mock));
Partial Mock in Laravel 5.3
$mock = Mockery::mock(App\User::class)->makePartial();
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2;
$this->assertEquals(2, $mock->roles);
You need to use a partial mock so the rest of the eloquent model will function as normal. You're just wanting to override a certain method basically.
$this->partialMock(User::class, function ($mock) {
$mock->shouldReceive('roles')->once();
});
you can use stdclass in this case.
$obj = new stdClass();
$obj->roles = 2;
$mock = Mockery::mock('User');
// return stdclass here.
$mock->shouldReceive('hasRole')->once()->andReturn($obj);
$mock->roles = 2;
$this->assertEquals(2, $mock->roles);

Unit testing with mocks and reflections in PHPUnit

I'm need to test protected method testMethod of class which uses some another method mockMethod. So I'm writing:
$class = $this->getMockBuilder('TestingClassName')
->setMethods(['mockMethod'])
->getMock();
$class->expects($this->any())
->method('mockMethod')
->will($this->returnValue('Hey!'));
$reflection = new \ReflectionObject($class);
$method = $reflection->getMethod('testMethod');
$method->setAccessible(true);
$result = $method->invokeArgs($class, $paramsArray);
But when method mockMethod invokes inside of testMethod it becomes like not mocked. It's protected method too and if I invoke it with another ReflectionMethod - I will give 'Hey!' from it.
Yes, the inner method was private, not protected.

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.

Categories