简体   繁体   中英

Mocking/Stubbing an Object of a class that implements arrayaccess in PHPUnit

Here is the constructor of the class I am writing a test suite for (it extends mysqli):

function __construct(Config $c)
{
    // store config file
    $this->config = $c;

    // do mysqli constructor
    parent::__construct(
        $this->config['db_host'],
        $this->config['db_user'],
        $this->config['db_pass'],
        $this->config['db_dbname']
    );
}

The Config class passed to the constructor implements the arrayaccess interface built in to php:

class Config implements arrayaccess{...}

How do I mock/stub the Config object? Which should I use and why?

Thanks in advance!

If you can easily create a Config instance from an array, that would be my preference. While you want to test your units in isolation where practical, simple collaborators such as Config should be safe enough to use in the test. The code to set it up will probably be easier to read and write (less error-prone) than the equivalent mock object.

$configValues = array(
    'db_host' => '...',
    'db_user' => '...',
    'db_pass' => '...',
    'db_dbname' => '...',
);
$config = new Config($configValues);

That being said, you mock an object implementing ArrayAccess just as you would any other object.

$config = $this->getMock('Config', array('offsetGet'));
$config->expects($this->any())
       ->method('offsetGet')
       ->will($this->returnCallback(
           function ($key) use ($configValues) {
               return $configValues[$key];
           }
       );

You can also use at to impose a specific order of access, but you'll make the test very brittle that way.

8 years after the question asked, 5 years after it was first answered I had the same question and came to a similar conclusion. This is what I did, which is basically the same as the second part of David's accepted answer, except I'm using a later version of PHPUnit.

Basically you can mock the ArrayAccess interface methods. Just need to remember that you probably want to mock both offsetGet and offsetExists (you should always check an array key exists before you use it otherwise you could encounter an E_NOTICE error and unpredictable behaviour in your code if it doesn't exist).



$thingyWithArrayAccess = $this->createMock(ThingyWithArrayAccess::class);

$thingyWithArrayAccess->method('offsetGet')
     ->with('your-offset-here')
     ->willReturn('test-value-1');

$thingyWithArrayAccess->method('offsetExists')
     ->with($'your-offset-here')
     ->willReturn(true);

Of course, you could have a real array in the test to work with, like


$theArray = [
    'your-offset-here-1' => 'your-mock-value-for-offset-1',
];

$thingyWithArrayAccess = $this->createMock(ThingyWithArrayAccess::class);

$thingyWithArrayAccess->method('offsetGet')
     ->willReturnCallback(
          function ($offset) use ($theArray) {
              return $theArray[$offset];
          }
     );

$thingyWithArrayAccess->method('offsetExists')
     ->willReturnCallback(
          function ($offset) use ($theArray) {
              return array_key_exists($offset, $theArray);
          }
     );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM