How to mockery mock internal method - php

Target.php
<?php
class Target
{
public function validate()
{
$this->getData();
return true;
}
public function getData()
{
return array();
}
}
TargetTest.php
<?php
class TargetTest extends PHPUnit_Framework_TestCase
{
public function testValidate()
{
$mock = m::mock('Target');
$mock->shouldReceive('getData')
->once();
$expected = $this->exp->validate();
$this->assertTrue($expected);
}
}
result
Mockery\Exception\InvalidCountException: Method getData() from Mockery_1_ExpWarning should be called
exactly 1 times but called 0 times.
I use Mockery as mock tool, the example always about how to mock with DI, I would like to know can I mock internal method?

You can use the Partial Mocks features of the testing framework for mock only the getData method and describe an expectation.
As (working) Example:
use Mockery as m;
class TargetTest extends \PHPUnit_Framework_TestCase
{
public function testValidate()
{
$mock = m::mock('Target[getData]');
$mock->shouldReceive('getData')
->once();
$expected = $mock->validate();
$this->assertTrue($expected);
}
}
Hope this help

Related

Mockery mock and spy called 0 times on custom class in controller

I'm having difficulty with the spy and mock in Laravel 7 test when I test for MyCustomClass.
I have tried both mock before running $this->get and spy after $this->get. Both with the same error message (*below).
When running debug in the controller the $myCustomClass is still the MyCustomClass and not the mocked object.
MyCustomClass
class MyCustomClass
{
public function execute()
{
return 'hello';
}
MyController
class MyController
{
public function show()
{
$myCustomClass = new MyCustomClass();
$data = $myCustomClass->execute();
return $data;
}
private $mySpy;
public function testAMethod()
{
$spy = $this->spy(MyCustomClass::class);
$response = $this->get('/my/path');
$spy->shouldHaveReceived('execute');
$response->assertStatus(200);
}
Error
Method execute(<Any Arguments>) from Mockery_2_App_MyCustomClass should be called
at least 1 times but called 0 times.
The problem is that you are instantiating MyCustomClass yourself with the new keyword.
In order for Laravel to be able to swap out the real class with the spy you have to use the service container.
Something like this:
class MyController
{
public function show(MyCustomClass $myCustomClass)
{
return $myCustomClass->execute();
}
}

How to make sure that dependency injection is working properly via TestCase

Provider
namespace App\Providers;
class ElasticSearchProvider extends ServiceProvider
{
public function register()
{
$hosts = [
'elasticsearch'
];
$instance = Elasticsearch\ClientBuilder::create()
->setHosts($hosts)
->build();
$this->app->instance('App\ESClient', $instance);
}
}
Actual Class
namespace App\Mappings;
class Categories implements Mappable
{
public $es;
public function __construct(App\ESClient $es)
{
$this->es = $es;
}
public function setMapping()
{
}
public function getMapping()
{
}
}
Test Case
use App\Mappings\Categories;
class CategoriesTest extends TestCase
{
private $instance;
public function testShouldReturnElasticSearchInstance()
{
$categories = new Categories();
dd($categories->es);
}
}
1) CategoriesTest::testShouldReturnElasticSearchInstance
ErrorException: Argument 1 passed to
App\Mappings\Categories::__construct() must be an instance of
App\Mappings\App\ESClient, none given,
So in here DI is not working, or i have register something wrong how can we test it ?
Thanks
You can use Mockery for test case. So in this example your test case would looks like:
use App\ESClient;
use App\Mappings\Categories;
use Mockery as m;
class CategoriesTest extends TestCase
{
private $instance;
public function testShouldReturnElasticSearchInstance()
{
$esClient = m::mock(ESClient::class);
$categories = m::mock(new Categories($esClient));
dd($categories->es);
}
}
This will provides you a mocked class of App\ESClient that you will inject into partialy mocked class of App\Mappings\Categories. When you learn to use mockery in your unit test you will find that this is the best opion for test purpose - because mock object (and Laravel Facades) can be override while testing and can catch every method call of the object in test (with shouldReceive method of the mocked class).

How to test the class method which use construct variable

Game.php
<?php
class Game
{
public $db;
public function __construct()
{
$this->db = new DB;
}
public function result()
{
return $this->db->data();
}
}
DB.php
<?php
class DB
{
public function data()
{
return false;
}
}
GameTest.php
<?php
use Mockery as m;
class GameTest extends PHPUnit_Framework_TestCase
{
public function testResult()
{
$game = m::mock(new Game);
$game->shouldReceive('data')
->once()
->andReturn(true);
$expected = $game->result();
$this->assertTrue($expected);
}
public function tearDown()
{
m::close();
}
}
This is my solution but totally not work, I guess if I want to get setting from __construct I need to mock a new class, I got message Failed asserting that false is true. which mean the mock thing is not work, how to deal with it?
You can't do it like that, best solution would be to use dependency injection for $db.
That way you can mock only DB like this...
$dbMock = m::mock('DB');
$dbMock->shouldReceive('data')
->once()
->andReturn(true);
Or you can keep you constructor like this (without DI), but you will have to mock that constructor also.
You can use Mockery by creating an "instance mock" for the DB class like this:
$dbMock = Mockery::mock('overload:MyNamespace\DB');
This will "intercept" when a new instance of the DB class is created and the $dbMock will be used instead. When the $dbMock is created you just need to add an expectation declaration for the given method:
$dbMock->shouldReceive('data')
->once()
->andReturn(true);

Undefined function when testing public functions called in the constructor of an abstract class using mockery

I have an abstract class which makes calls to public functions in its constructor. To test these calls are executed correctly, I have to mock the abstract class. Whenever I do this, I cannot make public function calls in the constructor. I get the the following error: BadMethodCallException: Method ::setData() does not exist on this mock object. The following code probably explains my issue better.
<?php
namespace Example\Tests;
use Mockery as m;
use PHPUnit_Framework_TestCase;
abstract class Foo
{
private $data;
public function __construct(array $defaults)
{
foreach ($defaults as $key => $value) {
$this->setData($key, $value);
}
}
public function getData()
{
return $this->data;
}
public function setData($key, $value)
{
return $this->data[$key] = $value;
}
}
class ExampleTest extends PHPUnit_Framework_TestCase
{
public function testDefaultsAreAssignedToData()
{
$expected = ['foo' => 'bar'];
$foo = m::mock('\Example\Tests\Foo', [$expected]);
$this->assertEquals($expected, $foo->getData());
}
}
Usually you are using mocks as a replacement for external dependencies for your class under test. In this case you just want to test the real behaviour of the class, and you are not dealing with any dependencies for the class Foo. In this case there is really no need to mock, I would just define a concrete instance of the abstract class for testing purposes.
That said, you can refactor to something like this.
class ConcreteFoo extends Foo
{
}
class ExampleTest extends PHPUnit_Framework_TestCase
{
public function testDefaultsAreAssignedToData()
{
$expected = ['foo' => 'bar'];
$foo = new ConcreteFoo($expected);
$this->assertEquals($expected, $foo->getData());
}
}

PHPUnit Stubbing Class methods declared as "final"

I'm writing a unit test for a class method that calls another class's method using a mock, only the method that needs to be called is declared as final, so PHPUnit is unable to mock it. Is there a different approach I can take?
example:
class to be mocked
class Class_To_Mock
{
final public function needsToBeCalled($options)
{
...
}
}
my test case
class MyTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$mock = $this->getMock('Class_To_Mock', array('needsToBeCalled'));
$mock->expects($this->once())
->method('needsToBeCalled')
->with($this->equalTo(array('option'));
}
}
Edit: If using the solution provided by Mike B and you have a setter/getter for the object you're mocking that does type checking (to ensure the correct object was passed into the setter), you'll need to mock the getter on the class you're testing and have it return the other mock.
example:
class to be mocked
class Class_To_Mock
{
final public function needsToBeCalled($options)
{
...
}
}
mock
class Class_To_MockMock
{
public function needsToBeCalled($options)
{
...
}
}
class to be tested
class Class_To_Be_Tested
{
public function setClassToMock(Class_To_Mock $classToMock)
{
...
}
public function getClassToMock()
{
...
}
public function doSomething()
{
$this->getClassToMock()
->needsToBeCalled(array('option'));
}
}
my test case
class MyTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$classToTest = $this->getMock('Class_To_Be_Tested', array('getClassToMock'));
$mock = $this->getMock('Class_To_MockMock', array('needsToBeCalled'));
$classToTest->expects($this->any())
->method('getClassToMock')
->will($this->returnValue($mock));
$mock->expects($this->once())
->method('needsToBeCalled')
->with($this->equalTo(array('option'));
$classToTest->doSomething();
}
}
I don't think PHPUnit supports stubbing/mocking of final methods. You may have to create your own stub for this situation and do some extension trickery:
class myTestClassMock {
public function needsToBeCalled() {
$foo = new Class_To_Mock();
$result = $foo->needsToBeCalled();
return array('option');
}
}
Found this in the PHPUnit Manual under Chapter 11. Test Doubles
Limitations
Please note that final, private and static methods cannot be stubbed or mocked. They are ignored by PHPUnit's test double functionality and retain their original behavior.
I just stumbled upon this issue today. Another alternative is to mock the interface that the class implements, given that it implements an interface and you use the interface as type hinting.
For example, given the problem in question, you can create an interface and use it as follows:
interface Interface_To_Mock
{
function needsToBeCalled($options);
}
class Class_To_Mock implements Interface_To_Mock
{
final public function needsToBeCalled($options)
{
...
}
}
class Class_To_Be_Tested
{
public function setClassToMock(Interface_To_Mock $classToMock)
{
...
}
...
}
class MyTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$mock = $this->getMock('Interface_To_Mock', array('needsToBeCalled'));
...
}
}

Categories