PhpUnit willReturnMap - PHP Review #4

Description:
A story about how a single little null cost me 30 minutes of debugging tests,
and then I found the truth in the documentation… which is completely silent about it. 🙃


🧐 The Problem

Let’s assume we have a method like this:

class MyService {
    public function doStuff(string $key, ?string $flag = null): string {
        return "Result: " . $key . ($flag ? " ($flag)" : "");
    }
}

And you want to test a class that uses MyService – of course, with a mock.
You use willReturnMap, because it’s elegant and works for multiple sets of arguments.

Sounds simple?
Let’s look at the test:

$mock = $this->createMock(MyService::class);

$mock->method('doStuff')
    ->willReturnMap([
        ['foo', 'bar', 'Result: foo (bar)'],
        ['baz', null, 'Result: baz'],
    ]);

Looks good, right?
NOPE. 😅


💥 What happens?

If you don’t pass exactly as many arguments as the method takes, PHPUnit will say:

“I have no idea what you mean” 🤷‍♂️
and return null.

So when your production code calls:

$service->doStuff('baz');

the mock will say:

“Sorry, I don’t know that map.”

And your test explodes. 💣


🤯 Why does this happen?

Because willReturnMap matches arguments by position, exactly 1:1.
A default null argument isn’t magically “guessed”.
You need to explicitly put it in the array.


✅ The Correct Solution

You need to match the exact number of arguments, even if they’re default:

$mock->method('doStuff')
    ->willReturnMap([
        ['foo', 'bar', 'Result: foo (bar)'],
        ['baz', null, 'Result: baz'], // <--- null is required!
    ]);

Yeah, I know – seems obvious, but sometimes you hope for a bit of magic. 🪄
Sadly – PHPUnit offers no magic here.


📝 The Moral of the Story

  • willReturnMap is great, but it’s very literal.
  • If the method takes 2 arguments – your map must have 2 arguments.
  • Even if the second argument defaults to null.
  • PHPUnit isn’t a fortune teller – it won’t guess you meant the default. 😅

🎉 Takeaways

After this discovery, my tests stopped exploding.
Knowing that you have to explicitly provide every argument in the map saved me hours of debugging.
Now I know – with mocks, it’s better to provide one argument too many than one too few.

🧠 Key Point: willReturnMap ≠ flexible matching.
It’s a strict argument map – provide everything or don’t complain when your tests burn. 🔥


✍️ PS: If anyone finds a mention of this case in the PHPUnit docs – let me know.
I couldn’t find it, and I searched half the internet. 🙈

Link to documentation




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • 🧐 Floats and the Mystery of -0.0 PHP Review #5
  • BackedEnum - PHP Review #3
  • Array Functions: Initializing an Array Using [...] or array()? - PHP Review #2
  • Array Functions: ['array_filter', 'array_merge'] - PHP Review #1
  • Testing Error Handling in Godot Using GUT