The question is in PHP, but applies to any language using the xUnit framework.
I want a mock, that expects 140 calls to method jump.
I need to verify, that at least once there is a call with 500 as parameter.
I don't care if all the calls are 500, but I need at least one that is called with 500.
$mock = $this->getMock('Trampoline', ['jump']);
$mock->expects($this->atLeastOnce())
->method('jump')
->with($this->equalTo(500))
->will($this->returnValue(true));
$sportsman->setTramploine($mock);
$sportsman->jumpToRandomHeights($times = 140); // this calls Trampoline->jump
// I need to verify the sportsman had jumped
// to the height of 500 at least once out of the 140 jumps he is performing
In the current code, the test fails after the first invocation of jump because the first invocation had a value different of 500, meaning the atLestOnce here only indicates that the method should be called, but not that it should be called with specific value among other calls.
Solution
The missing piece of information was using callbacks inside the with. Thanks to edorian's answer below this is what worked out:
$testPassed = false;
$checkMinHeight = function ($arg) use(&$testPassed)
{
if($arg === 500)
$testPassed = true;
// return true for the mock object to consider the input valid
return true;
}
$mock = $this->getMock('Trampoline', ['jump'])
->expects($this->atLeastOnce())
->method('jump')
->with($checkMinHeight)
->will($this->returnValue(true));
$sportsman->setTramploine($mock);
$sportsman->jumpToRandomHeights($times = 1000); // this calls Trampoline->jump
// I need to verify the sportsman had jumped
// to the height of 500 at least once out of the 1000 jumps he is performing
$this->assertTrue($testPassed, "Sportsman was expected to
jump 500m at least once");
You can but the best implementation withing PHPUnits mocking API, that I could come up with, still looks quite creepy.
Another way to solve this is a little more readable way would be to create your own subclass of Trampoline and implement it there.
But for the challenge:
Assuming this class:
<?php
class FancyMocking {
function doThing($value) { }
}
and that we have $x calls and one of those has to have a $value > 200:
<?php
class FancyMockingTest extends PHPUnit_Framework_TestCase {
public function testAtLeastOfMy200CallsShouldHaveAValueGreaterThan500() {
$maxInvocations = 200;
$mock = $this->getMock('FancyMocking');
$mock->expects($this->exactly($maxInvocations))
->method('doThing')
->with($this->callback(function ($value) use ($maxInvocations) {
static $invocationCount = 0;
static $maxValue = 0;
$maxValue = max($value, $maxValue);
/* The assertion function will be called twice by PHPUnit due to implementation details, so the *2 is a hack for now */
if (++$invocationCount == $maxInvocations * 2) {
$this->assertGreaterThan(200, $maxValue, 'in 500 tries the max value didn\'t to over 200');
}
return true;
}))
->will($this->returnCallback(function ($value) {
return $value >= 200;
}));
for($i = $maxInvocations - 2; $i; --$i) {
$mock->doThing(50);
}
var_dump($mock->doThing(250));
var_dump($mock->doThing(50));
}
}
This will produce:
PHPUnit 3.7.9 by Sebastian Bergmann.
.bool(true)
bool(false)
Time: 0 seconds, Memory: 2.75Mb
OK (1 test, 2 assertions)
Meaning the call with 250 returns true an the whole test case works.
If it fails:
To make it fail we change var_dump($mock->doThing(250)); to var_dump($mock->doThing(70)); and run it again:
PHPUnit 3.7.9 by Sebastian Bergmann.
Fbool(false)
Time: 0 seconds, Memory: 2.75Mb
There was 1 failure:
1) FancyMockingTest::testAtLeastOfMy200CallsShouldHaveAValueGreaterThan500
Expectation failed for method name is equal to <string:doThing> when invoked 200 time(s)
in 500 tries the max value didn't to over 200
Failed asserting that 70 is greater than 200.
.../FancyMockingTest.php:29
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
The solution that doesn't use PHPUnits mocking API would be something like
class FancyMockingFakeImplementation extends FancyMocking, using that instead of the mock and writing the custom logic there.
Related
I am using assertCount() method within my Symfony project and PHPUnit Functional tests. I was refactoring a method, and suddenly I does not work any more. But I am not sure what could be a problem.
I wanted to move my method that I am using multiple times from one class:
public function testPostSuccessful(): void
{
...
// rest of the request code //
$response = $this->client->getResponse();
$responseBody = json_decode($response->getContent(), true);
$this->assertArraySimilar($this->expected, $responseBody["data"][0]);
$this->assertEquals(200, $response->getStatusCode());
}
And in my other method:
protected function assertArraySimilar(array $expected, array $array)
{
// expected amount of key - value pairs are returned
$this->assertCount(6, $array);
...
// rest of the code //
}
This is throwing an error:
Failed asserting that actual size 2 matches expected size 6.
But when I dump the result like:
var_dump(count($array));die;
I get: int(6)
UPDATE: Also when I input 2 instead of 6, I got reverse error like: Failed asserting that 6 matches expected 2
What could be the problem here?
This works! Why is this working example and above one is not.
$this->assertCount(count($expected), $array);
With PHPUnit, I want to test that a function is called only once in Mocked Class. I tested several cases to be sure to understand excepts() :
functionInMock is not executed (Ok, expected result : no error) :
$myMock
->expects($this->never())
->method('functionInMock')
;
functionInMock is executed 1 times (Ok, expected result : no error) :
$myMock
->expects($this->once())
->method('functionInMock')
;
functionInMock is executed 2 times (Ok, expected result : error) :
$myMock
->expects($this->once())
->method('functionInMock')
;
functionInMock is executed 1 time :
$myMock
->expects($this->exactly(2))
->method('functionInMock')
;
or
$myMock
->expects($this->exactly(999))
->method('functionInMock')
;
Why don't I have an error in this last case ? The test passes without reporting an error.
I am no sure why you have unexpected behaviour but this example works fine
<?php
// Lets name it 'SampleTest.php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
class SampleTest extends TestCase
{
public function testSample(): void
{
$myMock = $this
->getMockBuilder(Sample::class)
->addMethods(['functionInMock'])
->getMock();
$myMock
->expects($this->exactly(2))
->method('functionInMock');
$myMock->functionInMock();
}
}
class Sample
{
public function function2InMock(): void
{
}
}
Execution
$ phpunit SampleTest.php
PHPUnit 9.1.1 by Sebastian Bergmann and contributors.
F 1 / 1 (100%)
Time: 125 ms, Memory: 6.00 MB
There was 1 failure:
1) SampleTest::testSample
Expectation failed for method name is "functionInMock" when invoked 2 time(s).
Method was expected to be called 2 times, actually called 1 times.
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
I am testing an parameter sent to a mocked event handler. The parameter is an object of "Event" sub-type, which itself has some data nested inside it. I want to test the Event and its substructure matches the fixture data I've injected into the code through various mocks.
I can test the "top level" of the event easily enough: the classname, and simple attributes like an event name string. I can also test that an attribute contains the same object, which I believe implicitly tests all the substructure of the object.
The problem I'm having is some of the sub-structure in a more complex example is causing the test to fail but it's irrelevant, so I want to cherry-pick specific properties of the sub-structure, and not just identity-compare the entire object.
I feel like I'm missing something in the attribute assertions: how to access the parameter that the "with" refers to - as variable. Then I could pass it into some of the assert methods like attributeEqualTo which require the item under test to be passed in. Perhaps these just cannot be used in the fluent case I'm using?
I'd like to check the event.data is a certain class.
I'd like to check the event.data.thing1 == X
I'd like to check the event.data.thing2 == Y
and so on.
Simplified code:
class MyEventData{
public $thing1;
public $thing2;
}
class MyEvent{
public $data;
}
// An event gets fired containing this in the tests
$eventData = new MyEventData(1,2);
$this->eventMock->expects($this->exactly(3))
->method('fire')
->with(
$this->logicalAnd(
// THIS WORKS OK
$this->isInstanceOf('\MyApp\MyEvents\SomeEvent'),
// THIS WORKS OK
$this->attributeEqualTo ('name', SomeEvent::EVENT_NAME),
// THIS WORKS in simplified cases only
$this->attributeEqualTo ('data', $eventData),
// HOW DO I GET THE "WITH" PARAMETER CONTEXT "INTO" THE THIRD PARAMETER?
$this->assertAttributeInstanceOf('\MyApp\MyEvents\MyEventData', 'data', -classOrObject- ),
// Then how can I test with attribute data.thing1 == 1 and data.thing2 = 2
)
);
I've got it to work using the callback constraint, but it feels like I've now stepped off the path and lost the power of PHPUnit - I can't seem to use the assertion helpers here anymore.
e.g. If the accumulated tests return false, I don't get any details in the output log beyond "Expectation failed for ... and is accepted by specified callback".
$this->callback(function($subject){
$b = true;
// I tried using this constraint but can't access (autoload) this class? So is it not supposed to be used directly?
//$c = new PHPUnit_Framework_Constraint_IsInstanceOf('\MyApp\MyEvents\MyEventData');
// return $c->matches(subject);
// this is the right assert, but it doesn't return the result, so I cannot use it in a callback constraint.
\PHPUnit_Framework_Assert::assertAttributeInstanceOf('\MyApp\MyEvents\MyEventData', 'data', $subject);
// This works but seems very "Manual"
$b = $b && get_class($subject->data) == '\MyApp\MyEvents\MyEventData';
$b = $b && $subject->data->thing1 == 1;
$b = $b && $subject->data->thing2 == 1;
return $b;
})
What is the best way to test for multiple array keys in a PHPUnit mock with() clause?
For example, to test whether a method calls 2nd argument is an array containing a 'foo' key:
$this->stubDispatcher->expects($this->once())
->method('send')
->with('className', $this->arrayHasKey('foo'));
What I'd like to do is something like $this->arrayHasKey('foo', 'bar') while not actually matching the exact contents of the array.
You can pass assertions directly to ->with() but they are named differently.
For the list take a look at the source: https://github.com/sebastianbergmann/phpunit/blob/3.5/PHPUnit/Framework/Assert.php#L2097 and following. Everything that doesn't start with "assert" and returns a new PHPUnit_Framework_*.
While i assume #David's answer works I think this approach, using logicalAnd() is a little cleaner / easier to read.
$mock->expects($this->once())->method("myMethod")->with(
$this->logicalAnd(
$this->arrayHasKey("foo"),
$this->arrayHasKey("bar")
)
);
Working example
<?php
class MyTest extends PHPUnit_Framework_TestCase {
public function testWorks() {
$mock = $this->getMock("stdClass", array("myMethod"));
$mock->expects($this->once())->method("myMethod")->with(
$this->logicalAnd(
$this->arrayHasKey("foo"),
$this->arrayHasKey("bar")
)
);
$array = array("foo" => 1, "bar" => 2);
$mock->myMethod($array);
}
public function testFails() {
$mock = $this->getMock("stdClass", array("myMethod"));
$mock->expects($this->once())->method("myMethod")->with(
$this->logicalAnd(
$this->arrayHasKey("foo"),
$this->arrayHasKey("bar")
)
);
$array = array("foo" => 1);
$mock->myMethod($array);
}
}
Output
phpunit assertArrayKey.php
PHPUnit 3.5.13 by Sebastian Bergmann.
.F
Time: 0 seconds, Memory: 6.50Mb
There was 1 failure:
1) MyTest::testFails
Expectation failed for method name is equal to <string:myMethod> when invoked 1 time(s)
Parameter 0 for invocation stdClass::myMethod(array( <string:foo> => <integer:1> )) does not match expected value.
Failed asserting that an array has the key <string:bar>.
/home/edo/test/assertArrayKey.php:27
You can use a callback to make multiple assertions.
$this->stubDispatcher->expects($this->once())
->method('send')
->will($this->returnCallback(function($class, $array) {
self::assertEquals('className', $class);
self::assertArrayHasKey('foo', $array);
self::assertArrayHasKey('bar', $array);
}));
Edit: And if you want to make basic assertions about some parameters and more complicated assertions about the others, you can add with().
$this->stubDispatcher->expects($this->once())
->method('send')
->with('className', $this->anything())
->will($this->returnCallback(function($class, $array) {
self::assertArrayHasKey('foo', $array);
self::assertArrayHasKey('bar', $array);
}));
To be super clear, you should never pass $this->returnCallback() to with(). Same for returnValue() and throwException(). These are all directives that tell the mock what to do when the method is called. with() is for telling the mock what parameters it should accept.
i used to have a function defined like this (that is working fine under ubuntu 9.10):
public function __toString( $surNameFirst = false) {
if ($this->givenName . $this->surname == '') return null;
else .......
}
after i have updated my machine to ubuntu 10.04( and php version Version: 5.3.2-1ubuntu4.2
) my app starts to show an error like this one ==>
Fatal error: Method Application_Model_Person::__tostring() cannot take arguments in /home/speshu/Development/where/application/models/Person.php on line 39
Call Stack:
0.0001 616576 1. {main}() /home/speshu/Development/where/public/index.php:0
0.0294 1008248 2. Zend_Application->bootstrap() /home/speshu/Development/where/public/index.php:35
0.0294 1008328 3. Zend_Application_Bootstrap_BootstrapAbstract->bootstrap() /usr/local/lib/ZendFramework-1.10.0/library/Zend/Application.php:355
0.0294 1008328 4. Zend_Application_Bootstrap_BootstrapAbstract->_bootstrap() /usr/local/lib/ZendFramework-1.10.0/library/Zend/Application/Bootstrap/BootstrapAbstract.php:582
0.0387 1991416 5. Zend_Application_Bootstrap_BootstrapAbstract->_executeResource() /usr/local/lib/ZendFramework-1.10.0/library/Zend/Application/Bootstrap/BootstrapAbstract.php:618
0.0387 1991776 6. Bootstrap->_initDoctrineCLI() /usr/local/lib/ZendFramework-1.10.0/library/Zend/Application/Bootstrap/BootstrapAbstract.php:665
0.0387 1991856 7. Bootstrap->_initDoctrine() /home/speshu/Development/where/application/Bootstrap.php:66
0.0406 2245200 8. Doctrine_Core::loadModels() /home/speshu/Development/where/application/Bootstrap.php:93
As of version 5.3 The __toString magic method can no longer accept arguments.
In any case should you really be doing that with a magic method? Why not declare toString($whatever) instead?
function __toString() {
if(func_num_args()>0) {
$surNameFirst=func_get_arg(0);
} else {
$surNameFirst=false;
}
....
This is how I got around the problem, it's dirty and ugly... but it worked to get the system working again before finding a more permanent solution.
You should be able to figure out how best to extend this to suite your needs, be aware that it's probably best to pass in an assosiative array if using this method, as it would be easier to access the data.
I think it's very ugly to invoke $obj->__toString();
it should be better to call just echo $obj ; if you don't want to pass arguments and call $obj->toString($param) ; if you want to specify arguments.
Another Possible work around.
class test
{
var $toStringArgs = array();
function SetToStringArgs()
{
$this->toStringArgs = func_get_args();
}
function __toString()
{
var_dump($this->toStringArgs);
}
}
$test = new test();
$test->SetToStringArgs('firstname','lastname');
$test->__toString();