I'm trying to mock a chain (nested) of methods to return the desired value , this is the code :
public function __construct($db)
{
$this->db = $db;
}
public function getResults()
{
return $this->db->getFinder()->find($this->DBTable);
}
I tried this mock but it does not work :
$dbMock = $this->createMock(DB::class);
$dbMock = $dbMock
->expects(self::any())
->method('getFinder')
->method('find')
->with('questions')
->will($this->returnValue('7'));
Any solutions how to solve such a problem ?
Thank you .
PHPUnit provides methods that are used to automatically create objects that will replace the original object in our test. createMock($type) and getMockBuilder($type) methods are used to create mock object. The createMock method immediately returns a mock object of the specified type.
How to Run Tests in PHPUnit. You can run all the tests in a directory using the PHPUnit binary installed in your vendor folder. You can also run a single test by providing the path to the test file. You use the --verbose flag to get more information on the test status.
Stub. Stubs are used with query like methods - methods that return things, but it's not important if they're actually called.
it's simpler now with Mocking Demeter Chains And Fluent Interfaces
simply
$dbMock = $dbMock
->expects(self::any())
->method('getFinder->find')
->with('questions')
->will($this->returnValue('7'));
another example from mockery docs
$object->foo()->bar()->zebra()->alpha()->selfDestruct();
and you want to make selfDestruct
to return 10
$mock = \Mockery::mock('CaptainsConsole');
$mock->shouldReceive('foo->bar->zebra->alpha->selfDestruct')->andReturn(10);
A chain consists of objects being called one after another. Therefore, You need to implement a chain of mocks. Just mock the methods in such a way that they return the mocked objects.
Something like this should work:
$finderMock = $this->createMock(Finder::class);
$finderMock = $finderMock
->expects(self::any)
->method('find')
->with('questions')
->will($this->returnValue('7'));
$dbMock = $this->createMock(DB::class);
$dbMock = $dbMock
->expects(self::any())
->method('getFinder')
->will($this->returnValue($finderMock));
Read more about mock chaining in this cool blog post.
I don't really see the point in testing chains, though. IMO it's better to limit tests to testing 1 module (function) or 2 modules (interaction) at a time.
While @BVengerov his answer will most definitely work, I suggest a design change instead. I believe that chaining mocks is not the way to go, it hurts readability and, more importantly, simplicity of your tests.
I propose that you make the Finder
class a member of your class. As such, you now only have to mock out the Finder
.
class MyClass {
private $finder;
public function __construct(Finder $finder) {
$this->finder = $finder;
}
public function getResults() {
return $this->finder->find($this->DBTable);
}
}
This change makes unittesting this function (and class!) simple.
"But I need the $db
variable in other places of the class!" Well, first and foremost, that probably indicates that a class in your current class is dying to be extracted. Keep classes small and simple.
However, as a quick-and-dirty solution, consider adding a setFinder()
setter, just to be used by tests.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With