Mock in PHPUnit - multiple configuration of the same method with different arguments

前端 未结 7 732
死守一世寂寞
死守一世寂寞 2020-11-29 22:26

Is it possible to configure PHPUnit mock in this way?

$context = $this->getMockBuilder(\'Context\')
   ->getMock();

$context->expects($this->any         


        
相关标签:
7条回答
  • 2020-11-29 22:38

    You can achieve this with a callback:

    class MockTest extends PHPUnit_Framework_TestCase
    {
        /**
         * @dataProvider provideExpectedInstance
         */
        public function testMockReturnsInstance($expectedInstance)
        {
            $context = $this->getMock('Context');
    
            $context->expects($this->any())
               ->method('offsetGet')
               // Accept any of "Matcher" or "Logger" for first argument
               ->with($this->logicalOr(
                    $this->equalTo('Matcher'),
                    $this->equalTo('Logger')
               ))
               // Return what was passed to offsetGet as a new instance
               ->will($this->returnCallback(
                   function($arg1) {
                       return new $arg1;
                   }
               ));
    
           $this->assertInstanceOf(
               $expectedInstance,
               $context->offsetGet($expectedInstance)
           );
        }
        public function provideExpectedInstance()
        {
            return array_chunk(array('Matcher', 'Logger'), 1);
        }
    }
    

    Should pass for any "Logger" or "Matcher" arguments passed to the Context Mock's offsetGet method:

    F:\Work\code\gordon\sandbox>phpunit NewFileTest.php
    PHPUnit 3.5.13 by Sebastian Bergmann.
    
    ..
    
    Time: 0 seconds, Memory: 3.25Mb
    
    OK (2 tests, 4 assertions)
    

    As you can see, PHPUnit ran two tests. One for each dataProvider value. And in each of those tests it made the assertion for with() and the one for instanceOf, hence four assertions.

    0 讨论(0)
  • 2020-11-29 22:40

    Sadly this is not possible with the default PHPUnit Mock API.

    I can see two options that can get you close to something like this:

    Using ->at($x)

    $context = $this->getMockBuilder('Context')
       ->getMock();
    
    $context->expects($this->at(0))
       ->method('offsetGet')
       ->with('Matcher')
       ->will($this->returnValue(new Matcher()));
    
    $context->expects($this->at(1))
       ->method('offsetGet')
       ->with('Logger')
       ->will($this->returnValue(new Logger()));
    

    This will work fine but you are testing more than you should (mainly that it gets called with matcher first, and that is an implementation detail).

    Also this will fail if you have more than one call to each of of the functions!


    Accepting both parameters and using returnCallBack

    This is more work but works nicer since you don't depend on the order of the calls:

    Working example:

    <?php
    
    class FooTest extends PHPUnit_Framework_TestCase {
    
    
        public function testX() {
    
            $context = $this->getMockBuilder('Context')
               ->getMock();
    
            $context->expects($this->exactly(2))
               ->method('offsetGet')
               ->with($this->logicalOr(
                         $this->equalTo('Matcher'), 
                         $this->equalTo('Logger')
                ))
               ->will($this->returnCallback(
                    function($param) {
                        var_dump(func_get_args());
                        // The first arg will be Matcher or Logger
                        // so something like "return new $param" should work here
                    }
               ));
    
            $context->offsetGet("Matcher");
            $context->offsetGet("Logger");
    
    
        }
    
    }
    
    class Context {
    
        public function offsetGet() { echo "org"; }
    }
    

    This will output:

    /*
    $ phpunit footest.php
    PHPUnit 3.5.11 by Sebastian Bergmann.
    
    array(1) {
      [0]=>
      string(7) "Matcher"
    }
    array(1) {
      [0]=>
      string(6) "Logger"
    }
    .
    Time: 0 seconds, Memory: 3.00Mb
    
    OK (1 test, 1 assertion)
    

    I've used $this->exactly(2) in the matcher to show that this does also work with counting the invocations. If you don't need that swapping it out for $this->any() will, of course, work.

    0 讨论(0)
  • 2020-11-29 22:44

    My 2 cents to the topic: pay attention when using at($x): it means that expected method call will be the ($x+1)th method call on the mock object; it doesn't mean that will be the ($x+1)th call of the expected method. This made me waste some time so I hope it won't with you. Kind regards to everyone.

    0 讨论(0)
  • 2020-11-29 22:46

    I just stumbled on this PHP extension to mock objects: https://github.com/etsy/phpunit-extensions/wiki/Mock-Object

    0 讨论(0)
  • 2020-11-29 22:46

    Here are also some solutions with the doublit library :

    Solution 1 : using Stubs::returnValueMap

    /* Get a dummy double instance  */
    $double = Doublit::dummy_instance(Context::class);
    
    /* Test the "offsetGet" method */
    $double::_method('offsetGet')
        // Test that the first argument is equal to "Matcher" or "Logger"
        ->args([Constraints::logicalOr('Matcher', 'Logger')])
        // Return "new Matcher()" when first argument is "Matcher"
        // Return "new Logger()" when first argument is "Logger"
        ->stub(Stubs::returnValueMap([['Matcher'], ['Logger']], [new Matcher(), new Logger()]));
    

    Solution 2 : using a callback

    /* Get a dummy double instance  */
    $double = Doublit::dummy_instance(Context::class);
    
    /* Test the "offsetGet" method */
    $double::_method('offsetGet')
        // Test that the first argument is equal to "Matcher" or "Logger"
        ->args([Constraints::logicalOr('Matcher', 'Logger')])
        // Return "new Matcher()" when first argument $arg is "Matcher"
        // Return "new Logger()" when first argument $arg is "Logger"
        ->stub(function($arg){
            if($arg == 'Matcher'){
                return new Matcher();
            } else if($arg == 'Logger'){
                return new Logger();
            }
        });
    
    0 讨论(0)
  • 2020-11-29 23:00

    As of PHPUnit 3.6, there is $this->returnValueMap() which may be used to return different values depending on the given parameters to the method stub.

    0 讨论(0)
提交回复
热议问题