PHP call a method on a ReflectionClass - php

I'm a novice developer trying to write a test suite for an existing Laravel app. Currently I'm trying to write tests for a controller but the authentication system used is strangely implemented and difficult to get around. I can't simply:
$user = \User::find(1);
$this->be($user);
I need to instantiate the controller and set a private property in its base class. To get around this, I'm trying to use reflection and setting the constructor properties and the user property manually. My issue arises when I try to invoke the method on the reflection object and I don't know how to get around it. My test method:
public function testCount()
{
$user = \User::find(1);
$this->be($user);
$leadRepositoryInterface = m::mock('CRM\Storage\Lead\LeadRepositoryInterface');
$response = m::mock('ColorJar\ApiResponse\Response');
$leadsController = new ReflectionClass('LeadsController');
$leadsControllerLead = $leadsController->getProperty('lead');
$leadsControllerLead->setAccessible(true);
$leadsControllerResponse = $leadsController->getProperty('response');
$leadsControllerResponse->setAccessible(true);
$leadsControllerCrmuser = $leadsController->getProperty('crmUser');
$leadsControllerCrmuser->setAccessible(true);
$leadsControllerLead->setValue($leadsController, $leadRepositoryInterface);
$leadsControllerResponse->setValue($leadsController, $response);
$leadsControllerCrmuser->setValue($leadsController, $user);
$reflectionMethod = new ReflectionMethod('ReflectionClass', 'count');
$this->assertEquals('jdsf', $reflectionMethod->invoke($leadsController));
}
generates the following error:
LeadsControllerTest::testCount
ReflectionException: Method ReflectionClass::count() does not exist
I realize that I'm calling count() on an instance of ReflectionClass instead of LeadsController but I don't know how else to set these properties, in particular the crmUser since it's a private property of the class that LeadsController inherits from. How do I make this work?

You should use PHPUnit's Mock feature.
https://phpunit.de/manual/current/en/test-doubles.html
Example 9.7: Stubbing a method call to return a value from a callback
will propably be helpful, you can configure, what result your call to "count" does.

Related

Is it possible to mock a method chained through an attribute?

I have the following PHP function, for which I am trying to write a PHPUnit test:
public function getDisplayMode()
{
if($this->request->query->get('master_video'))
{
return 'listTranslations';
}
else
{
return 'default';
}
}
The line $this->request->query->get('master_video') is what I am having trouble with.
I have mocked up the request object, so if it were just $this->request->get('master_video'), I could easily use the method method to tell my system what should be returned by get.
But I don't know how to do that when the query property is assumed to be present. (I don't know of any property method on my mock object, for example.)
Is there a way to define what should be returned by the get method here?
Create a mock of query and specify what happens on ->get(). The test would end up looking something like this:
public function testGetDisplayMode() {
$mockQuery = $this->getMockBuilder('Query') // Or whatever the query class is
->setMethods(['get'])
->getMock();
$mockQuery->expects($this->once())
->method('get')
->with('master_video')
->willReturn('foo');
$mockRequest = $this->getMockBuilder('Request') // Or whatever the request is
->getMock();
$mockRequest->query = $mockQuery;
$sut = new Bar($mockRequest) // Or however you instantiate the class with the mock request.
$mode = $sut->getDisplayMode();
// Do your assertions on the returned mode here.
}
As a general rule, I have found when I am doing things like this where I have a mock object returning a mock object that it is a code smell. Your method here doesn't need the $this->request it needs the query object. You should be giving this directly to the object or passing it into this method.
Without knowing more about the class that you are making, I can't give more advice.
If something is hard to write a test for, it is a sign that you have not optimally designed your code.

PHPUnit: mock all functions except one in ZendFramework2 Controller

I need to mock a zf2 controller and keep one real function: "getStopWords".
I tried this answer adapted below:
public function createMockExecpt()
{
// create mock to get names of all functions
$mock = $this->getMockBuilder('Controller\CollectionsController')->disableOriginalConstructor()->getMock();
$reflection = new MyReflectionClass($mock);
$functionsToMock = $reflection->getAllfunctionNamesExcept(["getStopWords"]);
// create mock but don't mock one function
return $this->getMock('Controller\CollectionsController', $functionsToMock);
}
but got an error about redefining a class.
// Cannot redeclare Mock_CollectionsController_d61a5651::__clone()
I think this happens because I need an instance of the controller to find out all the functions it has. However I can't make an instance of the controller in this context, which is why I need a mock. But I can't make more than one mock of a class within the same test, so I'm stuck.
My problem was that I thought you need an instance of the class to get all the methods.
Turns out all you need is the class name!
public function testGetStopWords()
{
// get the class methods the controller has, except getStopWords
$methodsToMock = array_diff(
get_class_methods("Controller\CollectionsController"),
["getStopWords"]
);
// use setMethods to determine which methods to mock
$mockController = $this->getMockBuilder("Controller\CollectionsController")
->setMethods($methodsToMock)
->disableOriginalConstructor()
->getMock();
}

PHPUnit turning an instance of a class into a mock after instantiation

Is there a way to create a mock class with PHPUnit which I can then create a new instance of by using its class name?
I have an interface which defines two methods. Something like:
interface FooInterface {
function getA();
function getB();
}
I then have another class which accepts a class name, creates an instance of that class, checks if it is an instance of what it expects (FooInterface) and then calls two methods on that class to get some information.
class FooInfo {
protected $a;
protected $b;
public function __construct($fooClass) {
$foo = new $fooClass;
if (!($foo instanceof FooInterface)) {
throw new \Exception();
}
$this->a = $foo->getA();
$this->b = $foo->getB();
}
}
I know how to mock an object just fine. The problem is, since this class accepts a class name, not an object (it is a part of a Manager which creates instances of the given class as needed), I can't use a normal mock object.
I tried to make a mock object then use that class name. It seems to create the object just fine, and even seems to have the functions I mocked out. However, it doesn't seem to follow the will($this->returnValue('myValue')) portion I set up later.
public function testConstruct()
{
$foo = $this->getMockForAbstractClass('Foo', array('getA', 'getB'));
$foo->expects($this->any())->method->('getA')->will($this->returnValue('a'));
$foo->expects($this->any())->method->('getB')->will($this->returnValue('b'));
$copyClass = get_class($foo);
$copy = new $copyClass();
// Passes
$this->assertTrue(method_exists($copy, 'getA');
// Fails, $copy->getA() returns null.
$this->assertEquals($copy->getA(), $foo->getA());
}
So, it does have the functions which were mocked, but they all return null.
Any ideas?
To use the new keyword in the constructor of a class is a rather bad habit exactly for the reasons you're experiencing now, even given your flexible use case.
Your test will not work because the mock you create will never be used, since your class will always create a real instance of the injected classname.
That said, what you want to do can be done with padraic's excellent mocking library mockery!
What you need is an 'instance mock':
$mock = \Mockery::mock('overload:MyNamespace\MyClass');
You can define your expectations on that mock which will be transferred to the real object, as soon as it is instantiated.
Integrating mockery with phpUnit is easy and well explained in the readme of the project.
And btw. each unit test should optimally make one assertion only!
The methods getA and getB return null because you have not specified what they should return. You did specify that for the abstract $foo by calling some methods. There's no way around that.
Since it's hard (if not impossible) to test the function, I would rewrite the code itself. Testing would be easy if your constructor would require a class instance rather than a class name. If you also must accept strings, you could write a few lines to check for a string input and create a class if a string is provided.

Is it Possible to PHPUnit Mock Object to Replace one Created in Class?

I've been trying to get the PHPUnit mock objects working for some legacy code I'm working on, but I'm having issues getting it to sub in for the object I'm after and I'm pretty sure it must be because I'm using it wrong.
Currently we have a class which is used purely for creating queries, when another class wants to make a query it creates an object of this class and passes it the sql and database details. What I want to do is have PHPUnit replace this object with a mock version which I can test.
What I've been finding though is, if I create the mock object in the test script, the method being tested just bypasses it. I'm guessing this is because the method is creating and then using the object locally rather than it being passed as a parameter (in which case I could just reference the method with the mock object passed as a parameter). Below is an example of what the code might look like:
class SampleClass{
function loadData(){
$sql = "SELECT * FROM users";
$query = new Query();
$query->query($sql);
while($row = $query->get_row()){
if($row['email'] = ''){
$this->errors[] = "Email missing from user ".$row['user_id'];
}
$result[] = $query->get_row();
}
$this->users = $result;
if(count($user->errors) >= 1){
return $user->errors;
}else{
return true;
}
}
}
class Query{
function query($sql){
$this->result = mysql_query($sql);
}
function get_row(){
return mysql_fetch_assoc($this->result);
}
}
Is there a way to create a mock object in the PHPUnit test file which will replace the $query object in SampleClass with a mock object which I can use to test the parameters being passed to and control the response of? I won't be able to replace the query class or change how it's referenced, as it's used extensively throughout our application, but I would like to at least be able to create some form of test framework for it. Would appreciate any help you can give
Edited to clarify that there's more than just the query happening in the loadData method, which is the part of the method I'm trying to test. I'm hoping to sub in a double of the query class method get_row which will return a pre-set array of sample data for the method to work with rather than it hitting an actual database
Yes that is possible.
However, you can just create a stub of the SampleClass your own. And you can make the classname or instance of Query a dependency, too. So you can actually inject it. I'd say that's a better way and much easier for you to write tests then.

Getting Instance Of Silex\Application

I am creating an application with Silex and was wondering if it is possible to somehow get the instance of the Silex\Application in a place where I can't do method_name(Application $application) in the method parameters?
For example, say I have a private method on a controller that is not an action. If I put Application $application as the parameter, it throws an error saying I need to pass it in.
I would rather not have to manually pass that method in if I don't have to.
There's really only two ways to do it.
a) Pass Silex\Application as an argument to the constructor of your class and assign it as an instance variable.
b) Pass the Silex\Application to your private method as an argument by hand.
Are you sure you need the full app in your class? The point of dependency injection is to inject dependencies directly, instead of injecting the container (yes, Silex\Application extends \Pimple, which is a Dependency Injection Container.
From your comment on the other answer, your goal of getting at the Silex/Application is to get at the Twig service there. The way that I've solved getting at the current application in another function for my Silex projects is:
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
class myController {
private $a;
// Route defined as:
// $app->get('/foo', 'myController::showPage');
public function showPage(Application $a) {
$this->a = $a;
return $this->doAwesome();
}
private function doAwesome() {
$twig = $this->a['twig'];
return $twig->render('awesomePage.twig');
}
}
Every function that is an endpoint for a route would then save the Application passed to it as a class property, for other functions to get at. It means you have to remember to do $this->a = $a; in every function that uses doAwesome() (before calling doAwesome()), but that's the cleanest way I've come up with to tackle that.

Categories