30

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.

2 Answers 2

32

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.

Sign up to request clarification or add additional context in comments.

3 Comments

what to do if the method handleValue is in another class ?
Good answer, I usually apply test dependencies when I have to use partial mocks between my test method and the mocked methods (if is impossible to refactor logic)
@Schleis: This approach can be helpful, if you just want to check, that a certain function gets called. It helped me to check for a certain regression that i had, because a certain function was not called. Using this allows to keep the test small.
14

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.

1 Comment

Update for 2023: OP's question reflected exactly my own situation. The above example no longer works (setMethods() no longer exists). You need to use onlyMethods() instead.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.