Zero Paskan
Zero Paskan

Reputation: 31

How to PHP Unit Test a function that calls an external class with a private constructor

This is an simplified version of the code I'm working with:

class FirstClass{
    public $theSecondObject;
    function exampleForTesting(){
        $this->theSecondObject = SecondClass::getInstance();
        return $this->theSecondObject->getRandomSentence();
    }
}

class SecondClass{
    private static $instance = null;
    private $randomSentence;
    private function __construct($someString){
        $this->randomSentence = $someString;
    }
    public static function getInstance(){
        if(self::instance === null){
            self::$instance = new SecondClass('Just a random sentence.');
        }
        return self::$instance;
    }
    public function getRandomSentence(){
        return $this->randomSentence;
    }
}

class FirstClassTestCase {
    public function startTest($method){
        $this->firstClassObject = new FirstClass();
    }
    public function testExampleForTesting(){
        $this->firstClassObject->theSecondObject = $this->getMock('SecondClass', ['getRandomSentence']);
        $this->firstClassObject->theSecondObject->expects($this->any()
            ->method('getRandomSentence')
            ->will($this->returnValue('A test unexpected sentence.'));
        $expected = 'Just a random sentence.';
        $actual = $this->firstClassObject->exampleForTesting();
        $this->assertNotEquals($expected, $actual);
    }
}

I have a FirstClass with a function exampleForTesting() that makes a call to a SecondClass() function whose return value I would like to mock for testing. However, the SecondClass() constructor is private, and running the test throws an error as a result:

Fatal Error Error:  Call to private SecondClass::__construct()

This seems to be due to the unique way that SecondClass objects are instantiated via the getInstance() function. Is there a potential way to get around this issue, without having to change the SecondClass constructor? The SecondClass is actually an external call in my application, so I'm not able to change it.

Also, I noticed that even when I tried to set the constructor to public to see if the test would pass, it actually fails, as the $actual variable generates the same value as $expected. Am I performing the return value mocking incorrectly?

Upvotes: 0

Views: 1067

Answers (1)

Barnabas Kecskes
Barnabas Kecskes

Reputation: 1961

One option would be to change your implementation and work with something called dependency injection.

class FirstClass
{
    public function __construct(SecondClass $secondClass) 
    {
        //...
    } 
}

This way you could mock the SecondClass easily in your test and pass it in as a parameter to the FirstClass.

class FirstClassTest {
    public function testExampleForTesting()
    {
        $mock = Mockery::mock(SecondClass::class);
        $mock->shouldReceive('getRandomSentence')
            ->andReturn('A test sentence.');
        $firstClassObject = new FirstClass($mock);
        // ...
    }
}

Sometimes it also makes sense to wrap the class you don't own as that often gives you even more flexibility.

Upvotes: 1

Related Questions