-
Notifications
You must be signed in to change notification settings - Fork 153
Make a test fail if a mocked method is not expected to be invoked (Reopen) #65
Comments
I solved this problem for me: class FinalConstraint extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder{
/**
* @var integer
*/
protected $expectedCount;
/**
* @param interger $expectedCount
*/
public function __construct($expectedCount)
{
$this->expectedCount = $expectedCount;
}
/**
* @param PHPUnit_Framework_MockObject_Invocation $invocation
* @throws PHPUnit_Framework_ExpectationFailedException
*/
public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation)
{
parent::invoked($invocation);
$count = $this->getInvocationCount();
if ($count > $this->expectedCount) {
$message = $invocation->toString() . ' unexpected';
throw new PHPUnit_Framework_ExpectationFailedException($message);
}
}
/**
* Returns a string representation of the object.
*
* @return string
*/
public function toString() {
return 'FinalConstraint';
}
/**
* Verifies that the current expectation is valid. If everything is OK the
* code should just return, if not it must throw an exception.
*
* @throws PHPUnit_Framework_ExpectationFailedException
*/
public function verify() {
$count = $this->getInvocationCount();
if($count != $this->expectedCount){
throw new PHPUnit_Framework_ExpectationFailedException(
sprintf(
'Methods of class was expected to be called %d times, ' .
'actually called %d times.',
$this->expectedCount,
$count
)
);
}
}
} And use: $universeMock->expects($this->at(0))->method("peopleIsHappy")->will($this->returnValue(false));
$universeMock->expects($this->at(1))->method("giveJoy");
$universeMock->expects(new FinalConstraint(2))->method(new PHPUnit_Framework_Constraint_IsAnything()); |
We can't change the way the If we make a new mockig API or a new entry point to the mocking API it's something I'd strongly consider but for now workarounds like the one showed here are the only option to get that behavior. I'd also consider a PR that adds something like But the main point is that, for BC reasons, we can't change the current behavior. So closing this. Sorry |
This is an old issue, but also 👍 from me for @ghost. I think it would be great to be able to create a mock object with a flag |
@edorian Would you still consider a PR with a method @ghost solution is nice but you still need to count how many times you've used |
Sad to see this closed as this could be a part of the MockBuilder. 👍 |
Has anything changed in recent phpunit versions that makes something in the line of what @sylfabre showed possible? |
@emil-nasso I've added this to our backlog, I'll try to make a PR once we're done with our upgrade to PHP 7 |
@sylfabre That is great news, i wish i could buy you a beer or even give you a kiss. :) |
Can i see the backlog anywhere or the status of this issue? |
Any news about this? |
Is this present in phpunit yet? Can i track it anywhere? |
Any news about this? |
Hi, i found this issue in the closed section : #9
I reopened it because i consider it pretty major.
Here is an example, i want to test this :
Now here's the test for the doMaintenance Method.
So if you run it as is, now, everything is fine.
Now imagine there's a troll that edits the UniverseController code and accidentally calls the killEveryone method as in this :
You can confirm that it's a terrible error. Now rerun the test, still passes. Even though the $universe is a mock, it's really dangerous to ignore "unexpected" invocations.
The same thing happen if you take the other way around, if initially the doMaintenance() method behavior WAS to killEveryone() and you forget to expect it, the test will run, and someone come and see this line makes no sense, remove it and tests still good. So basically you can change the "contract" of a method without breaking tests.
So to me it is clear the test SHOULD fail, and i don't really understand the argument emitted in the issue i linked above. If it breaks a lot of test to fail unexpected mocked methods calls, then i'd say the test aren't really viable, they're assuming a mocked method call to return null when not expected at all.
If it would be too big to change this, maybe there could be a sort of "strict" mock that would NOT allow unexpected methods to be called.
I checked the code of the PHPUnit_Framework_MockObject_InvocationMocker::invoke method and it clearly isn't trivial to make sure the $invocation->methodName has at least once matcher attached to it, but my example above shows a clear flaw in the mocking system.
What do you think?
The text was updated successfully, but these errors were encountered: