Search code examples
phparraysunit-testingphpunitarray-key

Unit Testing: Give an invalid array key that doesn't break PHP. Possible?


I'm working on a unit test for a small class. I'm using this class to get down and dirty with PHPUnit so I can start properly test bigger pieces of code that I write in the future.

Consider the following code I am trying to test:

/**
 * Registers a starting benchmark tick.
 *
 * Registers a tick with the ticks registry representing the start of a benchmark timeframe.
 *
 * @param string $id The identifier to assign to the starting tick.  Ending tick must be the same.
 * @return bool Returns TRUE if a tick was registered successfully or FALSE if it was not.
 * @since 0.1
 */
public function start($id)
{
    $this->tick($id . "_start");
    if($this->getStart($id) != false) {
        return true;
    }
    return false;
}

/**
 * Retrieves a registered start tick.
 *
 * Checks to see if a start tick is registered.  If found the microtime value (as a float) is
 * returned, otherwise FALSE is returned.
 *
 * @param string $id The identifier to lookup the tick under.
 * @return mixed The microtime (as a float) assigned to the specified tick or FALSE if the tick
 * start hasn't been registered.
 * @since 0.1
 */
public function getStart($id)
{
    if(isset($this->ticks[$id . "_start"])) {
        return $this->ticks[$id . "_start"];
    }
    return false;
}

And the following is the actual test code:

public function testBadStartTick()
{
    $this->assertFalse($this->bm->start("What_Invalid_Key_Fits_Here?"))
}

The problem is that this test function always returns true no matter how many times I try to make it return false. I have tried giving null values, keys of 300+ characters, empty arrays, even the instance of a new object. In all cases PHP either breaks or throws some kind of warning. When PHP doesn't break, my value gets converted to something that PHP will accept in the array key and then my test doesn't pass when attempting to do a $this->assertFalse().

I would like to achieve the highest amount of code coverage possible.

So my question is if these methods, given their current code, will ever return false under normal operation?

I'm thinking that because I am appending text (which is for management purposes) I am always supplying some kind of key that PHP will accept regardless of what I give for $id.

Any thoughts?

Thanks in advance!


Solution

  • You will need to use mocks to do this.

    It will require you to do this 2 parts to get the full coverage.

    But you will have to do this

    public function testBadStartTick()
    {
        $bm = $this->getMock('bm', array('getStart'));
    
        $bm->expects($this->any())
             ->method('getStart')
             ->will($this->returnValue(false));
    
        $this->assertFalse($bm->start("What_Invalid_Key_Fits_Here"))
    }
    

    Of course replace bm for your actual classname (if needed with namespace). Like this you will mock the functionality of getStart for that test only and you can test what your result would be.

    Of course if you want full coverage and test everything you will need as well

    public function testGoodStartTick()
    {
        $this->assertTrue($this->bm->start("What_Invalid_Key_Fits_Here"))
    }
    

    This will test the path if everything goes good. Because you don't mock there anymore it will use the actual getStart function.

    public function testMissingStartTick()
    {
        $this->assertFalse($this->bm->getStart("What_Invalid_Key_Fits_Here"));
    }
    

    This will test the getStart() independent of the other function and because of that the key wont exist and will return false.

    The true path you should not have to test separate because you will test that with testGoodStartTick()

    Edit: As people have said in the comments and i should have put that here as a warning it is never wise to have code that will never be executed. As you had a check there i thought tick() might do some magic that sometimes will not add the key to the array but i understand from your comment that that is not the case. So indeed in your case my answer is not the correct one and is best to follow the suggestion of removing the code that you know will always be true.