Question

Given the following class:

<?php
class Example {
    private $Other;

    public function __construct ($Other)
    {
        $this->Other = $Other;
    }

    public function query ()
    {   
        $params = array(
            'key1' => 'Value 1'
            , 'key2' => 'Value 2'
        );

        $this->Other->post($params);
    }
}

And this testcase:

<?php
require_once 'Example.php';
require_once 'PHPUnit/Framework.php';

class ExampleTest extends PHPUnit_Framework_TestCase {

    public function test_query_key1_value ()
    {   
        $Mock = $this->getMock('Other', array('post'));

        $Mock->expects($this->once())
              ->method('post')
              ->with(YOUR_IDEA_HERE);

        $Example = new Example($Mock);
        $Example->query();
    }

How do I verify that $params (which is an array) and is passed to $Other->post() contains a key named 'key1' that has a value of 'Value 1'?

I do not want to verify all of the array - this is just a sample code, in actual code the passed array has a lot more values, I want to verify just a single key/value pair in there.

There is $this->arrayHasKey('keyname') that I can use to verify that the key exists.

There is also $this->contains('Value 1'), which can be used to verify that the array has this value.

I could even combine those two with $this->logicalAnd. But this of course does not give the desired result.

So far I have been using returnCallback, capturing the whole $params and then doing asserts on that, but is there perhaps another way to do what I want?

Was it helpful?

Solution 3

I ended up creating my own constraint class, based on the attribute one

<?php
class Test_Constraint_ArrayHas extends PHPUnit_Framework_Constraint
{
    protected $arrayKey;

    protected $constraint;

    protected $value;

    /**
     * @param PHPUnit_Framework_Constraint $constraint
     * @param string                       $arrayKey
     */
    public function __construct(PHPUnit_Framework_Constraint $constraint, $arrayKey)
    {
        $this->constraint  = $constraint;
        $this->arrayKey    = $arrayKey;
    }


    /**
     * Evaluates the constraint for parameter $other. Returns TRUE if the
     * constraint is met, FALSE otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     * @return bool
     */
    public function evaluate($other)
    {
        if (!array_key_exists($this->arrayKey, $other)) {
            return false;
        }

        $this->value = $other[$this->arrayKey];

        return $this->constraint->evaluate($other[$this->arrayKey]);
    }

    /**
     * @param   mixed   $other The value passed to evaluate() which failed the
     *                         constraint check.
     * @param   string  $description A string with extra description of what was
     *                               going on while the evaluation failed.
     * @param   boolean $not Flag to indicate negation.
     * @throws  PHPUnit_Framework_ExpectationFailedException
     */
    public function fail($other, $description, $not = FALSE)
    {
        parent::fail($other[$this->arrayKey], $description, $not);
    }


    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString ()
    {
        return 'the value of key "' . $this->arrayKey . '"(' . $this->value . ') ' .  $this->constraint->toString();
    }


    /**
     * Counts the number of constraint elements.
     *
     * @return integer
     */
    public function count ()
    {
        return count($this->constraint) + 1;
    }


    protected function customFailureDescription ($other, $description, $not)
    {
        return sprintf('Failed asserting that %s.', $this->toString());
    }

It can be used like this:

 ... ->with(new Test_Constraint_ArrayHas($this->equalTo($value), $key));

OTHER TIPS

The $this->arrayHasKey('keyname'); method exists but its name is assertArrayHasKey :

// In your PHPUnit test method
$hi = array(
    'fr' => 'Bonjour',
    'en' => 'Hello'
);

$this->assertArrayHasKey('en', $hi);    // Succeeds
$this->assertArrayHasKey('de', $hi);    // Fails

In lieu of creating a re-usable constraint class, I was able to assert an array key's value using the existing callback constraint in PHPUnit. In my use case, I needed to check for an array value in the second argument to a mocked method (MongoCollection::ensureIndex(), if anyone is curious). Here's what I came up with:

$mockedObject->expects($this->once())
    ->method('mockedMethod')
    ->with($this->anything(), $this->callback(function($o) {
        return isset($o['timeout']) && $o['timeout'] === 10000;
    }));

The callback constraint expects a callable in its constructor, and simply invokes it during evaluation. The assertion passes or fails based on whether the callable returns true or false.

For a large project, I'd certainly recommend creating a re-usable constraint (as in the above solution) or petitioning for PR #312 to be merged into PHPUnit, but this did the trick for a one-time need. It's easy to see how the callback constraint might be useful for more complicated assertions, too.

In case you wish to do some complex testing on the parameter, and also have useful messages and comparisons, there is always the option of placing assertions within the callback.

e.g.

$clientMock->expects($this->once())->method('post')->with($this->callback(function($input) {
    $this->assertNotEmpty($input['txn_id']);
    unset($input['txn_id']);
    $this->assertEquals($input, array(
        //...
    ));
    return true;
}));

Notice that the callback returns true. Otherwise, it would always fail.

Sorry, I'm not an English speaker.

I think that you can test if a key exists in the array with the array_key_exists function, and you can test if the value exists with array_search

For example:

function checkKeyAndValueExists($key,$value,$arr){
    return array_key_exists($key, $arr) && array_search($value,$arr)!==false;
}

Use !== because array_search return the key of that value if exists and it may be 0.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top