35

I'm trying to create a mock instance in setUp with default values for all of the overridden methods and then in several different tests change the return value for some of the methods depending on what I'm testing without having to set up the entire Mock. Is there a way to do this?

This is what I tried, but the naive approach doesn't work. The method still returns the value from the original expectation setup.

First setup:

$my_mock->expects($this->any())
        ->method('one_of_many_methods')
        ->will($this->returnValue(true));

In another test before a different assert:

$my_mock->expects($this->any())
        ->method('one_of_many_methods')
        ->will($this->returnValue(false));

Duplicate to this question: PHPUnit Mock Change the expectations later, but that one got no responses and I thought a new question might bring the issue to the fore.

Community
  • 1
  • 1
Brian Ramsay
  • 7,536
  • 8
  • 41
  • 52
  • AFAIK unfortunately there is no such possibility with phpunit. You can use for example $my_mock->__phpunit_hasMatchers(), but it's not exactly what you want. Of course you can set different return values on the same method with a) "at" matcher or b) "returnCallback" but they depend on a) order of invocation b) call parameters.. but is also not what you are looking for. I will let you know i figure out something new. – Cyprian Nov 29 '12 at 22:54
  • See also http://stackoverflow.com/questions/5484602/mock-in-phpunit-multiple-configuration-of-the-same-method-with-different-argum/5484864#5484864 – bishop Jan 20 '15 at 16:21

5 Answers5

14

In cases where you use the same method more than once, you should use the "at" declaration with the proper count where executed in the code. This way PHPUnit knows which one you mean, and can fulfill the expectation/assertion properly.

The following is a generic example where method 'run' is used several times:

public function testRunUsingAt()
    {
        $test = $this->getMock('Dummy');

        $test->expects($this->at(0))
            ->method('run')
            ->with('f', 'o', 'o')
            ->will($this->returnValue('first'));

        $test->expects($this->at(1))
            ->method('run')
            ->with('b', 'a', 'r')
            ->will($this->returnValue('second'));

        $test->expects($this->at(2))
            ->method('run')
            ->with('l', 'o', 'l')
            ->will($this->returnValue('third'));

        $this->assertEquals($test->run('f', 'o', 'o'), 'first');
        $this->assertEquals($test->run('b', 'a', 'r'), 'second');
        $this->assertEquals($test->run('l', 'o', 'l'), 'third');
    }

I think this is what you're looking for, but if I'm misunderstanding please let me know.

Now in terms of mocking anything, you can mock it as many times as you want, but you are not going to want to mock it with the same name as in the setup, else every time you use it you are referring to the setup. If you need to test similar methods in different scenarios, then mock it for each test. You could create one mock in the setup, yet for one test use a different mock of a similar item within an individual test, but not of the global name.

jsteinmann
  • 4,502
  • 3
  • 17
  • 21
  • 9
    I think this should be the accepted answer, but please also note that the index you use for at() is based on the total number of calls to that mock object, NOT the number of calls to that particular method. So if other calls are made to other methods on the same object, you will need to increase the index accordingly. – Russ Jul 10 '14 at 10:25
  • The question was how to change mocked method behavior in a specified test. The counters like `at()` is not applicable here cause each test can be run independently in _any order_. And in every test need to get the expected behavior of the mocked method. I have the same problem with a deep mocking relations. And I didn't find the answer for now. – Vijit Oct 04 '19 at 14:43
  • `at()` is deprecated in PHPUnit 9 and will be removed in PHPUnit 10. https://github.com/sebastianbergmann/phpunit/issues/4297 – Samuel Gfeller Mar 09 '21 at 16:16
  • good answere, does not help me unfortunately. as for now ( 2022 ), we should have 1 specific test per test function. not 3 tests like in the example. it's much easier to track during CI, when "emptyParameterTest" fails – clockw0rk Jul 29 '22 at 13:20
10

You could do this using a lambda callback:

$one_of_many_methods_return = true;
$my_mock->expects($this->any())
        ->method('one_of_many_methods')
        ->will(
             $this->returnCallback(
                 function () use (&$one_of_many_methods_return) {
                     return $one_of_many_methods_return;
                 }
              )         
          );
$this->assertTrue($my_mock->one_of_many_methods());

$one_of_many_methods_return = false;

$this->assertFalse($my_mock->one_of_many_methods());    

Note the & in the use statement.

gamag
  • 423
  • 5
  • 14
  • This is useful for one-off values, although it's easier for test methods to share properties (`$this->one_of_many_methods_return`) than references (`&$one_of_many_methods_return`). You can also use methods directly, like `$this->returnCallback([$this, 'someMethod'])`. Also note that PHP < 5.5 may complain about using `$this` and private/protected properties/methods in anonymous functions. – Warbo Sep 18 '14 at 10:06
1

I have not tried this, but could you not set the Mock up in the Setup, then in each of the tests:

public function testMethodReturnsTrue
{
    $this->my_mock->will($this->returnValue(true));
    $this->assertTrue( ... );
    ...
}

I am not sure if this will work, as I am trying to set the will() method in the test, not when the initial mock was created.

Steven Scott
  • 10,234
  • 9
  • 69
  • 117
1

Rather than trying to override mocked methods, I find it easier to override the mocked objects themselves. For example:

class ThingTest extends \PHPUnit_Framework_TestCase
    public function setUp()
    {
        $this->initFoo();
        $this->initBar();
    }

    public function testOne()
    {
        // Uses default [method => value] map for foo and bar
        $this->assertSomething($this->thing->someMethod());
    }

    public function testTwo()
    {
        // Override foo's map
        $this->initFoo(['method1' => 'some other value']);
        $this->assertSomethingElse($this->thing->someMethod());
    }

    public function testThree()
    {
        // Override bar explicitly, so we can use 'once'
        $this->initBar([]);
        $this->bar->expects($this->once())
                  ->method('method1');
        $this->thing->someOtherMethod();
    }

    private function initFoo($methods = null)
    {
        $this->init('foo',
                    $this->getMock('Foo'),
                    is_null($methods)? ['method1' => 'default value 1']
                                    : $methods);
    }

    private function initBar($methods = null)
    {
        $this->init('bar',
                    $this->getMock('Bar'),
                    is_null($methods)? ['method1' => 'default value 1']
                                     : $methods);
    }

    private function init($name, $object, $methods)
    {
        $this->$name = $object;
        foreach ($methods as $method => $value) {
            $this->$name->expects($this->any())
                        ->method($method)
                        ->will($this->returnValue($value));
        }
        $this->thing = new Thing($this->foo, $this->bar);
    }
}
Warbo
  • 2,611
  • 1
  • 29
  • 23
-1

You can also run the tests in a separate process:

/**
 * @runTestsInSeparateProcesses b/c we change the return value of same expectation
 * @see http://stackoverflow.com/questions/13631855
 */
class ThingTest extends \PHPUnit_Framework_TestCase
{
    public function setUp() {
        $this->config = Mockery::mock('alias:Config');
    }

    public function test_thing_with_valid_config() {
        $this->config_set('default', 'valid');
        $sut = new \Thing();
    }

    /**
     * @dataProvider provides_broken_configs
     * @expectedException \RuntimeException
     */
    public function test_thing_with_broken_config($default) {
        $this->config_set('default', $default);
        $sut = new \Thing();
    }

    public function provides_broken_configs() {
        return [ [ null ] ];
    }

    protected function config_set($key, $value) {
        $this->config->shouldReceive('get')->with($key)->andReturn($value);
    }
}

In this example, I happen to be using Mockery, but the pattern is the same. Because each test has fresh memory each run through, we don't encounter the limitation of "overriding" previously set expectations.

bishop
  • 37,830
  • 11
  • 104
  • 139