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

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.

Related

Setting value for inner method in php unit

I can not set value for inner method when I try to test. Here I have written a sample class. I have created mock object for same class but does not effect.
class A
{
public function OneTest()
{
if($this->TwoTest()){
return true;
}
}
public function TwoTest()
{
// return somethings
}
}
I am new at phpunit test writing. if some one expert help me that good for me. I want to test this method. I have tried with:
class ATest extends \PHPUnit_Framework_TestCase
{
public function testOne()
{
$helper = new testOne();
// trying to set TwoTest() method value but does not effect.
$mock = $this->createMock(A::class);
$mock->method("TwoTest")
->willReturn(true);
$this->assertTrue($helper->OneTest();
}
}
Actually I do not know how to use my mocking method result. My actual implementation in twoTest method contains some db related code. I do not want to run db code in testing time.
You are pretty close with your mock. What you want to do is called partial mocking. This is done by creating a mock of A with only TwoTest being mocked, i.e. it will now always return true and never actually call the real code inside the original implementation in A, whereas all other methods still act as before. Therefore calling $mock->OneTest() should return the expected result. Since you make both calls on the (partially) mocked instance, you won't need $helper. So your test would probably look something like this:
public function testOneWhenTwoTestReturnsTrue()
{
$mock = $this->getMockBuilder(A::class)
->setMethods(["TwoTest"])
->getMock();
$mock->method("TwoTest")
->willReturn(true);
$this->assertTrue($mock->OneTest();
}
Notice that I use getMockBuilder() instead of just createMock() and setMethods() is what we need for your test. We only overwrite the one method we want to mock, the rest will behave as defined in the original class. To quote the docs:
setMethods(array $methods) can be called on the Mock Builder object to specify the methods that are to be replaced with a configurable test double. The behavior of the other methods is not changed. If you call setMethods(null), then no methods will be replaced.

PHP call a method on a ReflectionClass

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.

How to mock based on method parameter

One of my stubbed mock objects has a method which will be called twice in a method that I want to test. How can I write the tests so that both branches in my test method will be coveraged? Code sample (The stubbed object is the cache):
public function myMethodToTest($param, $default) {
if ($this->cache->has($param)) {
return 'A';
} else if ($this->cache->has($default)) {
return 'B';
}
}
Lifted from the phpunit documentation, we can start with this example:
public function testObserversAreUpdated()
{
// Create a mock for the Observer class,
// only mock the update() method.
$observer = $this->getMockBuilder('Observer')
->setMethods(array('update'))
->getMock();
// Set up the expectation for the update() method
// to be called only once and with the string 'something'
// as its parameter.
$observer->expects($this->once())
->method('update')
->with($this->equalTo('something'));
// Create a Subject object and attach the mocked
// Observer object to it.
$subject = new Subject('My subject');
$subject->attach($observer);
// Call the doSomething() method on the $subject object
// which we expect to call the mocked Observer object's
// update() method with the string 'something'.
$subject->doSomething();
}
Pay attention to the with() method call. You can use that to specify an expectation that the method will be called with a specific parameter value and dictate what to return when it happens. In your case, you should be able to do something like this:
$cacheStub->method('has')
->with($this->equalTo('testParam1Value'))
->willReturn(true);
Do that in one test, and you'll test one branch of your code. In a separate test, you can set up the mock differently:
$cacheStub->method('has')
->with($this->equalTo('testParam2Value'))
->willReturn(true);
This test will test your other branch. You can combine them into a single test if you like, you may have to recreate the mock in between assertions.
See also this short article that has some alternative ways of calling with() other than $this->equalTo()

Stuck on writing PHPUnit getMock unit test

I'm admittedly very rusty with unit testing.
I have the following function in my Registration.php:
protected function _showModal($request) {
if (Store::isUserSupported()) {
return false;
}
return true;
}
I started writing the following test but I know I'm missing some key items:
public function testUserSupported() {
$mockStore = $this->getMockClass('Store', array('isUserSupported'));
$mockStore::staticExpects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$mockStore::isUserSupported();
$plugin = $this->getMockBuilder('Registration')
->setMethods(array('_showModal'))
->disableOriginalConstructor()
->getMock();
$plugin = $this->getPublicClass($plugin);
$plugin->expects($this->once())
->method('_showTermsModal')
->will($this->returnValue(true));
}
The $mockStore part is getting called, but not sure how to tie it into my $plugin call.. I want to write a test to mock Store::isUserSupported() returning true within the showModal function. Any words of advice?
You should avoid static calls in your code, because they make your code coupled together and hard to change and hard to maintain. It also makes it harder to tests, as you are suffering here.
Instead of static calls, pass the collaborators needed to work. In your code example, the class you want to test would receive as parameter the Store class, instead of calling it statically.
By doing this change, you can now create a mock for the method isUserSupported() of the Store class, and pass the mock to the object under test, that will efectively use the mocked object.
$mockStore = $this->getMock('Store', array('isUserSupported'));
$mockStore->expects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$object_under_test = new Registration( $mockStore );
$object_under_test->showModal($request); // Only in case showModal is public, otherwise call public method
If the Store class is only needed for that method, and you don't want to pass it as dependency in the constructor, you can pass it on the method itself.
$mockStore = $this->getMock('Store', array('isUserSupported'));
$mockStore->expects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$object_under_test = new Registration();
$object_under_test->showModal($mockStore, $request); // Only in case showModal is public, otherwise call public method
Also, you shouldn't test protected/private methods of your classes, since they are low level implementation details that your tests don't need to know about. You should only make calls to public methods in your tests. Otherwise, the tests become very coupled with the real implementation, and if you refactor your code, you will most likely have to change the tests too.

phpunit testing method that calls other class methods which need mock

I'm trying to create a pretty standard unit test where I call a method and assert it's response, however the method I'm testing calls another method inside the same class which does a little bit of heavy lifting.
I want to mock that one method but still execute the method I'm testing as is, only with the mocked value returned from the call to the other method.
I've dumbed down the example to make it as simple as possible.
class MyClass
{
// I want to test this method, but mock the handleValue method to always return a set value.
public function testMethod($arg)
{
$value = $arg->getValue();
$this->handleValue($value);
}
// This method needs to be mocked to always return a set value.
public function handleValue($value)
{
// Do a bunch of stuff...
$value += 20;
return $value;
}
}
My attempt at writing the tests.
class MyClassTest extends \PHPUnit_Framework_TestCase
{
public function testTheTestMethod()
{
// mock the object that is passed in as an arg
$arg = $this->getMockBuilder('SomeEntity')->getMock();
$arg->expects($this->any())
->method('getValue')
->will($this->returnValue(10));
// test handle document()
$myClass = new MyClass();
$result = $myClass->testMethod($arg);
// assert result is the correct
$this->assertEquals($result, 50);
}
}
I have tried mocking the MyClass object, but when I do that and call the testMethod it always returns null. I need a way to mock the one method but leave the rest of the object intact.
You can mock the class that you are testing and specify the method that you want to mock.
$mock = $this->getMockBuilder('MyClass')
->setMethods(array('handleValue'))
->getMock();
$mock->expects($this->once())
->method('handleValue')
->will($this->returnValue(23)) //Whatever value you want to return
However, IMO this is not the best idea for your tests. Testing like this will make refactoring much more difficult. You are specifying the implementation of the class rather than the behavior that the class is supposed to have. If handleValue is doing a lot of complicated work that makes testing difficult, consider moving the logic into a separate class and injecting that into your class. Then you can create a mock of that class and pass it in to testMethod. Doing so will give you the added advantage of making MyClass more extensible if handleValue needs to adapt its behavior.
http://www.oodesign.com/strategy-pattern.html
As a general rule, you should not mock the system that you are testing.
You can specify which methods to mock (partial mock) with setMethods():
// Let's do a `partial mock` of the object. By passing in an array of methods to `setMethods`
// we are telling PHPUnit to only mock the methods we specify, in this case `handleValue()`.
$csc = $this->getMockBuilder('Lightmaker\CloudSearchBundle\Controller\CloudSearchController')
->setConstructorArgs($constructor)
->setMethods(array('handleValue'))
->getMock();
// Tell the `handleValue` method to return 'bla'
$csc->expects($this->any())
->method('handleValue')
->with('bla');
Any other methods in the class not specified in the array you give setMethods() will be executed as is. If you do not use setMethods all methods will return NULL unless you specifically set them.

Categories