skip to Main Content

I want to test (with PHPUnit) a method that contains a foreach loop. I want the full patch coverage. The original code is little too complex, so I created a minimal example below to illustrate my problem. There are 3 cases for the foreach loop in the PHP. (I use the PHP 8.2.)

  1. Empty iterable something.
  2. Not empty iterable something.
  3. Not iterable something.
    public function dodo(array $a)
    {
        foreach ($a as $one) {
            return $one;
        }

        return null;
    }

It is easy to cover first two: a not empty array and an empty array. But how can I pass a not iterable something as the function argument? I have tried few ways, but I’ve got the TypeError.

$something->dodo(null); # TypeError

call_user_func([$something, 'dodo'], null); # TypeError

$rClass = new ReflectionClass($something);
$rMethod = $rClass->getMethod('dodo');
$rMethod->invoke($something, null); # TypeError

I don’t want to remove or change the type from the method definition. This would make the code a little less readable. Is there a way around? How can I write a test that will cover all cases of the foreach loop?

In other words:
How i can call the dodo with an argument of a wrong type? I want to write test with very high code paths coverage.

2

Answers


  1. When you add array as a type declaration, you basically tell PHP to make sure that no other type is allowed to be passed to your method. And the way PHP makes sure that no other type is passed, is by throwing that TypeError you are seeing.

    In a way your code is equivalent to the following:

    public function dodo($a)
    {
        if (!is_array($a)) {
            throw new TypeError(...);
        }
    
        foreach ($a as $one) {
            return $one;
        }
    
        return null;
    }
    

    You basically answered your own question. You can call the method with a non-array argument just like you did: $something->dodo(null);. The TypeError you are seeing is the expected result.

    And since PHP is already taking care that you are only working with arrays here, you have in fact covered all cases of that foreach loop.

    Login or Signup to reply.
    1. Empty iterable something.
    2. Not empty iterable something.
    3. Not iterable something.

    This is incorrect. These are not your paths. I’m not sure where you got the idea that "Path 3" has to do with passing a non-iterable to a typehinted iterable. The three paths that XDebug is reporting are:

    1. Enter the loop, return a value from within the loop.

    This is tested by calling dodo() with a non-empty iterable.

    1. Skip over the loop, return null.

    This is tested by calling dodo() with an empty iterable.

    1. Enter the loop, exit the loop, return null.

    This can not be covered by a test because your loop never exits. So, you don’t actually have three paths. However, XDebug doesn’t know that. To avoid this odd issue, I’d simply rewrite the method so that it is not indeterminate:

    public function dodo(array $a)
    {
        return $a[0] ?? null;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search