How to mock based on method parameter - php

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()

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.

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.

Mock a method within a model method in CakePHP

I am running CakePHP 2.8.X, and am trying to write a unit test for a Model function.
Let's call the model Item, and I'm trying to test its getStatus method.
However, that model makes a call to its find within the getStatus method.
So something like this:
class Item extends Model
{
public function getStatus($id) {
// Calls our `$this->Item-find` method
$item = $this->find('first', [
'fields' => ['status'],
'conditions' => ['Item.id' => $id]
]);
$status = $item['status'];
$new_status = null;
// Some logic below sets `$new_status` based on `$status`
// ...
return $new_status;
}
}
The logic to set "$new_status" is a bit complex, which is why I want to write some tests for it.
However, I'm not entirely sure how to override the find call within Item::getStatus.
Normally when I want to mock a Model's function, I use $this->getMock coupled with method('find')->will($this->returnValue($val_here)), but I don't want to completely mock my Item since I want to test its actual getStatus function.
That is, in my test function, I'm going to be calling:
// This doesn't work since `$this->Item->getStatus` calls out to
// `$this->Item->find`, which my test suite doesn't know how to compute.
$returned_status = $this->Item->getStatus($id);
$this->assertEquals($expected_status, $returned_status);
So how do I communicate to my real Item model within my test that it should override its internal call to its find method?
I knew this had to be an issue others have faced, and it turns out PHPUnit has a very easy way to address this!
This tutorial essentially gave me the answer.
I do need to create a mock, but by only passing in 'find' as the methods I'd like to mock, PHPUnit helpfully leaves all other methods in my Model alone and does not override them.
The relevant part from the above tutorial is:
Passing an array of method names to your getMock second argument produces a mock object where the methods you have identified
Are all stubs,
All return null by default,
Are easily overridable
Whereas methods you did not identify
Are all mocks,
Run the actual code contained within the method when called (emphasis mine),
Do not allow you to override the return value
Meaning, I can take that mocked model, and call my getStatus method directly from it. That method will run its real code, and when it gets to find(), it'll just return whatever I passed into $this->returnValue.
I use a dataProvider to pass in what I want the find method to return, as well as the result to test against in my assertEquals call.
So my test function looks something like:
/**
* #dataProvider provideGetItemStatus
*/
public function testGetItemStatus($item, $status_to_test) {
// Only mock the `find` method, leave all other methods as is
$item_model = $this->getMock('Item', ['find']);
// Override our `find` method (should only be called once)
$item_model
->expects($this->once())
->method('find')
->will($this->returnValue($item));
// Call `getStatus` from our mocked model.
//
// The key part here is I am only mocking the `find` method,
// so when I call `$item_model->getStatus` it is actually
// going to run the real `getStatus` code. The only method
// that will return an overridden value is `find`.
//
// NOTE: the param for `getStatus` doesn't matter since I only use it in my `find` call, which I'm overriding
$result = $item_model->getStatus('dummy_id');
$this->assertEquals($status_to_test, $result);
}
public function provideGetItemStatus() {
return [
[
// $item
['Item' => ['id' = 1, 'status' => 1, /* etc. */]],
// status_to_test
1
],
// etc...
];
}
one way to mock find could be to use a test specific subclass.
You could create a TestItem that extends item and overrides find so it doesn't perform a db call.
Another way could be to encapsulate the new_status logic and unittests it independent of the model

Symfony Mock a specific method in my unit test

I have problem when trying to mock a service in order to unit test it.
in my test class I have,
$mock = $this->getMock('MyClass');
$mock->expects($this->any())->method('method_2')->will($this->returnValue('fake_value'));
in my service, method_2() calls another method (let's say method_1()')
As I want to unit test only method_1() I need to mock only method_2()
but when I am running this test, method_1() already returns null.
Do you have any idea on why I'm already getting null?
Take a look at getMock() helper signature, it allows you to pass an array of methods as a second argument, those methods are then mocked and returns null (unless you define the valus each one of them should return)
In your case all the methods are mocked and return null except method_2() for which you forced the returned value.
Try again and replace,
$mock = $this->getMock('MyClass');
by,
$mock = $this->getMock('MyClass', array('method_2'));

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