Search code examples
phpunitmockery

How to mock class which within other class


Connect.php

class Connect
{
    private $dbName;

    public function __construct($dbName)
    {
        $this->dbName = $dbName;
    }

    public function getQuery()
    {
        return $this->dbName;
    }
}

ConnectFacade.php

class ConnectFacade
{
    public $dbA;
    public $dbB;
    public $dbC;
    public $dbD;

    public function __construct()
    {
        $this->dbA = new Connect('connect to database a');
        $this->dbB = new Connect('connect to database b');
        $this->dbC = new Connect('connect to database c');
        $this->dbD = new Connect('connect to database d');
    }
}

Member.php

class Member
{
    private $connect;

    public function __construct(ConnectFacade $connect)
    {
        $this->connect = $connect;
    }

    public function data()
    {
        return $this->connect->dbA->getQuery();
    }
}

index.php

require 'vendor/autoload.php';

echo (new Member(new ConnectFacade))->data();

In my case, I have many tables from many databases need to be connected in one class, therefore I need to declare many connections with different connection name very often, so I put it to ConnectFacade for saving time, I know it's kind of complicated, but that's the structure of my company, this sample code will show connect to database a on index.php, of course in reality the connection is doing a lot of stuffs, my question is I don't know how mock on this situation, this is what I tried

use Mockery as m;

class MemberTest extends PHPUnit_Framework_TestCase
{
    public function testData()
    {
        $connect = m::mock('ConnectFacade');
        $connect->shouldReceive('dbA->getQuery')
            ->once()
            ->andReturn(true);
        $actual = (new Member($connect))->data();
        $this->assertTrue($actual);
    }

    public function tearDown()
    {
        m::close();
    }
}

and I got Call to a member function getQuery() on a non-object in D:\www\phpunit\class\Member.php on line 14, I don't know how to make the test pass.


Solution

  • The problem is that dbA is not a method on ConnectFacade, but rather a variable. Therefore $connect->shouldReceive('dbA->getQuery') won't work.

    Instead you need to set the dbA variable as a mock and then change the expectation to just receive getQuery:

    public function testData()
    {
        $connect = m::mock('ConnectFacade');
        $connect->dbA = $connect;
        $connect->shouldReceive('getQuery')
            ->once()
            ->andReturn(true);
        $actual = (new Member($connect))->data();
        $this->assertTrue($actual);
    }
    

    Another option would be to instead mock the creation of the Connect class in ConnectFacade instead of mocking the whole ConnectFacade:

    public function testData()
    {
        $connect = m::mock('overload:Connect');
        $connect->shouldReceive('getQuery')
            ->once()
            ->andReturn(true);
        $connectFacade = new ConnectFacade;
        $actual = (new Member($connectFacade))->data();
        $this->assertTrue($actual);
    }