Improved error messages when mock expectations are not met, but there were other method calls
I've been using Mockery for a while, and I've had an idea for an improvement I'd like to see. I've forked the repo and have put some time in to make a bit of a proof of concept, but I wanted to create this issue to see if there is any interest/feedback on this from other users and the maintainers as it might be a bit of a bigger change :)
My biggest complaint when using Mockery comes from mocking methods which have more than a few parameters. When creating the mocks, or when working on code that has tests using said mocks, it can be quite tricky to figure out why a mock expectation was not met.
I would love it if Mockery's failed expectation error during test execution could provide more context as to why the mock assertion failed. I also do front end work and use Jest for testing, and I quite like how that handles this situation.
Here is an example PHP Class I want to mock:
class Sunflower
{
private int $potWidth;
private string $potShape;
public function repot(int $potWidth, string $potShape): self
{
$this->potWidth = $potWidth;
$this->potShape = $potShape;
return $this;
}
}And here is an example test:
public function testCanRepotASunflower(): void
{
$sunflowerMock = Mockery::mock(Sunflower::class);
$sunflowerMock
->shouldReceive("repot")
->with([
"potWidth" => 5,
"potShape" => "square", // parameter exists, but the actual value is different
])
->once();
$sunflowerMock->repot(5, "circle");
}This test will fail due to the $potShape parameter of the repot method not matching an expectation - we were expecting "square" to be provided rather than "circle".
This is the feedback that Mockery gives:
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_1_Nature_Sunflower::repot(5, 'circle'). Either the method was unexpected or its arguments matched no expected argument list for this methodIn this trivial example, it's easy to spot the issue. However, we don't actually know what was provided instead of the expected value, so now I'd need to go through my code and start adding logs / dumps to try and find the culprit. This issue gets worse when there are more method parameters as there's now more things I might need to investigate.
What prompted me to raise this issue and work on it was a long day of fixing mock expectations in a lot of tests for a method which had 8 parameters I found myself thinking this would be so much easier and quicker if the errors presented like they do in Jest.
For comparison, Jest is able to show more context as to why this failed. Here is an equivalent test in Jest:
const Sunflower = require("./Sunflower");
jest.mock("./Sunflower");
test("can repot a sunflower", () => {
const sunflower = new Sunflower();
sunflower.repot(5, "circle");
expect(sunflower.repot).toHaveBeenCalledWith(5, "square");
});And this is the output:
● can repot a sunflower
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: 5, "square"
Received: 5, "circle"
Number of calls: 1I have a working prototype that brings a similar thing to Mockery, this is what I have right now, using the same test setup above:
Mockery\Exception\NoMatchingExpectationException: Expected value 'square' for the potShape argument, received 'circle'I will probably tweak it to look more like the Jest output, but for now as a proof of concept I think this is a good start.
I'd also like for it to be able to present unmatched array params using a diff in the output, like Jest does (example uses a JS object, but this style of output for PHP assoc arrays would be nice):
● can repot a sunflower
expect(jest.fn()).toHaveBeenCalledWith(...expected)
- Expected
+ Received
Object {
- "potShape": "square",
+ "potShape": "circle",
"potWidth": 5,
},Is this something others would find helpful, and would there be interest from the maintainers to have this change in Mockery?
Also, for the maintainers - this is the first time I've dug into the source code of Mockery, so if there's anything I should be particularly aware of (some scenarios this would need to support etc) I'd appreciate some pointers!
Source: mockery/mockery