PHPUnit and Mock Objects not working - php

I am not sure if I am doing something wrong or it is a bug with PHPUnit and mock objects. Basically I am trying to test if $Model->doSomething() is called when $Model->start() is triggered.
I am using Ubuntu in a VirtualBox, and phpunit 1.1.1 installed via pear.
The full code is below. Any help would be appreciated, it's driving me crazy.
<?php
require_once 'PHPUnit/Autoload.php';
class Model
{
function doSomething( ) {
echo 'Hello World';
}
function doNothing( ) { }
function start( ) {
$this->doNothing();
$this->doSomething();
}
}
class ModelTest extends PHPUnit_Framework_TestCase
{
function testDoSomething( )
{
$Model = $this->getMock('Model');
$Model->expects($this->once())->method('start'); # This works
$Model->expects($this->once())->method('doSomething'); # This does not work
$Model->start();
}
}
?>
The output from PHPUnit:
There was 1 failure:
1) ModelTest::testDoSomething
Expectation failed for method name is equal to <string:doSomething> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

As you found, you need to tell PHPUnit which methods to mock. Also, I would avoid creating expectations for methods that you are calling directly from the test. I would write the above test like this:
function testDoSomething( )
{
$Model = $this->getMock('Model', array('doSomething');
$Model->expects($this->once())->method('doSomething');
$Model->start();
}

Just to expand on why David Harkness's answer works, if you do not specify the $methods parameter to getMock then all functions in the class are mocked. Incidentally, you can confirm this with:
class ModelTest extends PHPUnit_Framework_TestCase
{
function testDoSomething( )
{
$obj = $this->getMock('Model');
echo new ReflectionClass(get_class($obj));
...
}
}
So, why does it fail? Because your start() function has been mocked too! I.e. the function body you have given has been replaced, and so your $this->doSomething(); line never gets run.
Hence, when there are any functions in your class that you need to be preserved, you'll have to explicitly give the list of all other functions.

Related

Is buffer the right way to test that a method echoes something?

Suppose to have a class "Debug" with a "log" function that MUST output exactly the message passed to the function. Is there a good way to test it?
class DebugTest extends PHPUnit_Framework_TestCase
{
public function test()
{
ob_start();
$d = new Debug;
$d->log('foo');
$this->assertEquals(
'foo',
ob_get_clean()
);
}
}
Are there alternatives?
The best way (as of 2017) to check the output of the tested code is to use the function expectOutputString():
class DebugTest extends PHPUnit_Framework_TestCase
{
public function test()
{
// Set the expectation
$this->expectOutputString('foo');
// Run the tested code
$d = new Debug;
$d->log('foo');
}
}
Behind the scene, expectOutputString() does the buffering trick for you.
The method is available since PHPUnit 3.6.0 (that was released more than 5 years ago.)

how to test if the correct function is called in phpunit

I am trying to learn how to write tests using phpunit.
In the test case i am trying to write i am testing 3 methods. Test is if method 1 returns false then call method 2 and 3 else just stop.
class MyTest {
$mock1->getMock('some class1')
$mock1->expect($this->once())
->method('method1')
->will($this->returnValue(false));
$mock2->getMock('some_class2')
$mock2->expect($this->once())
->method('method2')
$mock2->method2($arg)
$mock2->expect($this->once())
->method('method3')
$mock3->method3($arg)
}
how do i test if method 2 and 3 were called.
Currently i am getting a failure that No Test was Found
Look at the documentation, roughly this is how your class should look like:
class MyTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
//initialize objects for each method to work with
}
public function testFeature()
{
$arg = 'something';
$mock1->getMock('some class1');
$mock1->expect($this->once())
->method('method1')
->will($this->returnValue(false));
$mock2->getMock('some_class2');
$mock2->expect($this->once())
->method('method2');
$mock2->method2($arg);
$mock2->expect($this->once())
->method('method3');
//$mock3->method3($arg);
}
}

Testing code that uses get_class with PHPUnit mock objects

Using PHPUnit and a mock object, I am trying to test some code that uses get_class to determine if an object is included by a filter or not.
Here is the class to be tested:
class BlockFilter implements FilterInterface
{
private $classes;
public function __construct(array $classes = array())
{
$this->classes = $classes;
}
public function isIncluded(NodeTraversableInterface $node)
{
if (Type::BLOCK != $node->getDocumentType()) {
return false;
}
if (! empty($this->classes)) {
/*** HERE IS THE PROBLEM: ***/
return in_array(get_class($node), $this->classes);
}
return true;
}
}
Here is the method from my unit test:
public function testIfContainerBlockIsIncluded()
{
$containerBlock = $this->getMock('Pwn\ContentBundle\Document\ContainerBlock');
$containerBlock->expects($this->any())->method('getDocumentType')->will($this->returnValue(Type::BLOCK));
$filter = new BlockFilter(array('Pwn\ContentBundle\Document\ContainerBlock'));
$this->assertTrue($filter->isIncluded($containerBlock));
}
The mock object $containerBlock behaves like the real object Pwn\ContentBundle\Document\ContainerBlock; even code using instanceof works (because PHPUnit makes it a subclass of the real class, I believe).
The code being tested uses get_class to get a string value of the class and compare it with an array of expected class names. Unfortunately, for the mock object, get_class returns something like this:
Mock_ContainerBlock_ac231064
(the _ac231064 suffix changes on each invocation).
This causes my test to fail, so what are my options?
Rework the code to avoid using get_class? This implies get_class should not be used when trying to write testable code.
Use a real instance of the ContainerBlock class instead of a mock? This means we are effectively testing both classes at once.
Some other awesomely clever trick that you're all going to suggest??? ;)
Thanks for any help...
Pass the Mock's class name in the test:
new BlockFilter(array(get_class($this->containerBlock)));

Testing methods in abstract classes with arguments

In my TDD project I am trying to test a method in an abstract class.
abstract class Database_Mapper_Abstract
{
public function setTable($sTablename){
return('foo');
}
}
This is the way I wrote my simple test:
public function testCanSetTable(){
$oMock = $this->getMockForAbstractClass('JCMS_Database_Mapper_Abstract');
$oMock->expects($this->once())
->method('setTable')
->with($this->equalTo('foo'))
->will($this->returnValue('foo'));
$this->assertEquals('foo',$oMock->setTable());
}
When I run this test i get the following error:
PHPUnit 3.5.13 by Sebastian Bergmann.
E
Time: 1 second, Memory: 6.75Mb
There was 1 error:
1)
Database_Mapper_AbstractTest::testCanSetTable
Missing argument 1 for
Database_Mapper_Abstract::setTable(), called in
K:\xampp\htdocs\tests\library\Database\Mapper\Abstract.php
on line 15 and defined
K:\xampp\htdocs\library\Database\Mapper\Abstract.php:4
K:\xampp\htdocs\tests\library\Database\Mapper\Abstract.php:15
FAILURES! Tests: 1, Assertions: 0,
Errors: 1.
The way I understand this is that it can't find the argument for the setTable function.
But I set it with the with() method. I also tried with('foo'). That also doesn't help me.
Does anyone have an idea?
Testing an abstract class:
For testing an abstract class you don't want to use the "create behavior methods".
Just getMockForAbstractClass() like this:
<?php
abstract class JCMS_Database_Mapper_Abstract
{
public function setTable($sTablename){
return $sTablename."_test";
}
}
class myTest extends PHPUnit_Framework_TestCase {
public function testCanSetTable(){
$oMock = $this->getMockForAbstractClass('JCMS_Database_Mapper_Abstract');
$this->assertEquals('foo_test', $oMock->setTable('foo'));
}
}
You just use the mocking functionality to create an instance of that abstract class and test against that.
It's only a shortcut for writing
class MyDataMapperAbstractTest extends JCMS_Database_Mapper_Abstract {
// and filling out the methods
}
The actual error:
What happens is that you have a method with one parameter:
public function setTable($sTablename){
but you call it with zero paremters:
$oMock->setTable()
so you get an error from PHP and if PHP throws a warnings PHPUnit will show you an error.
Reproduce:
<?php
abstract class JCMS_Database_Mapper_Abstract
{
public function setTable($sTablename){
return('foo');
}
}
class myTest extends PHPUnit_Framework_TestCase {
public function testCanSetTable(){
$oMock = $this->getMockForAbstractClass('JCMS_Database_Mapper_Abstract');
$oMock->expects($this->once())
->method('setTable')
->with($this->equalTo('foo'))
->will($this->returnValue('foo'));
$this->assertEquals('foo',$oMock->setTable());
}
}
Results in:
phpunit blub.php
PHPUnit 3.5.13 by Sebastian Bergmann.
E
Time: 0 seconds, Memory: 3.50Mb
There was 1 error:
1) myTest::testCanSetTable
Missing argument 1 for JCMS_Database_Mapper_Abstract::setTable(), called in /home/.../blub.php on line 19 and defined
Fixing
Change:
$this->assertEquals('foo',$oMock->setTable());
to
$this->assertEquals('foo',$oMock->setTable('foo'));
then you don't get a PHP Warning and it should work out :)

PHPUnit "Mocked method does not exist." when using $mock->expects($this->at(...))

I've run into a strange issue with PHPUnit mock objects. I have a method that should be called twice, so I'm using the "at" matcher. This works for the first time the method is called, but for some reason, the second time it's called, I get "Mocked method does not exist.". I've used the "at" matcher before and have never run into this.
My code looks something like:
class MyTest extends PHPUnit_Framework_TestCase
{
...
public function testThis()
{
$mock = $this->getMock('MyClass', array('exists', 'another_method', '...'));
$mock->expects($this->at(0))
->method('exists')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$mock->expects($this->at(1))
->method('exists')
->with($this->equalTo('bar'))
->will($this->returnValue(false));
}
...
}
When I run the test, I get:
Expectation failed for method name is equal to <string:exists> when invoked at sequence index 1.
Mocked method does not exist.
If I remove the second matcher, I do not get the error.
Has anyone run into this before?
Thanks!
The issue ended up being with how I understood the "at" matcher to work. Also, my example was not verbatim how it is in my unit test. I thought the "at" matcher counter worked on a per query basis, where it really works on a per object instance basis.
Example:
class MyClass {
public function exists($foo) {
return false;
}
public function find($foo) {
return $foo;
}
}
Incorrect:
class MyTest extends PHPUnit_Framework_TestCase
{
public function testThis()
{
$mock = $this->getMock('MyClass');
$mock->expects($this->at(0))
->method('exists')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$mock->expects($this->at(0))
->method('find')
->with($this->equalTo('foo'))
->will($this->returnValue('foo'));
$mock->expects($this->at(1))
->method('exists')
->with($this->equalTo('bar'))
->will($this->returnValue(false));
$this->assertTrue($mock->exists("foo"));
$this->assertEquals('foo', $mock->find('foo'));
$this->assertFalse($mock->exists("bar"));
}
}
Correct:
class MyTest extends PHPUnit_Framework_TestCase
{
public function testThis()
{
$mock = $this->getMock('MyClass');
$mock->expects($this->at(0))
->method('exists')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$mock->expects($this->at(1))
->method('find')
->with($this->equalTo('foo'))
->will($this->returnValue('foo'));
$mock->expects($this->at(2))
->method('exists')
->with($this->equalTo('bar'))
->will($this->returnValue(false));
$this->assertTrue($mock->exists("foo"));
$this->assertEquals('foo', $mock->find('foo'));
$this->assertFalse($mock->exists("bar"));
}
}
FYI, Not sure if its related, but I encountered the same thing, but not with the $this->at() method, for me it was the $this->never() method.
This raised the error
$mock->expects($this->never())
->method('exists')
->with('arg');
This fixed the error
$mock->expects($this->never())
->method('exists');
It did the same thing when using the $this->exactly(0) method.
Hope this help someone.
Try changing $this->at(1) to $this->at(2)
This is an unfortunate wording of the error message by PHPUnit.
Double check the order of your calls, like #rr's answer mentions.
For me, as far as I know with my own code, I should be using at(0) and at(1) respectively, but it wasn't until I used at(2) and at(3) instead that it worked. (I'm using session mocking in CakePHP.)
The best way to check the order is to get 'into' the called method and check what's passed. You can do that like this:
$cakePost = $this->getMock('CakePost');
$cakePost->expects($this->once())
->method('post')
->with(
// Add a line like this for each arg passed
$this->callback(function($arg) {
debug("Here's what was passed: $arg");
})
);
As far as i can tell from the Demo code it should work. I produced a working example in case you are running an older PHPUnit Version and want to check that way if it works for you too.
In case that doesn't help maybe you could provide a bit more (at best executable) code ? :)
<?php
class MyTest extends PHPUnit_Framework_TestCase
{
public function testThis()
{
$mock = $this->getMock('MyClass');
$mock->expects($this->at(0))
->method('exists')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$mock->expects($this->at(1))
->method('exists')
->with($this->equalTo('bar'))
->will($this->returnValue(false));
$this->assertTrue($mock->exists("foo"));
$this->assertFalse($mock->exists("bar"));
}
}
class MyClass {
public function exists($foo) {
return false;
}
}
printing
phpunit MyTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 4.25Mb
OK (1 test, 3 assertions)
Are you sure you included MyClass in your test? I have had some undefined method errors when mocking a class/interface without including it.
May be not when question was raised however today documentation clearly specifies how the at should be used and I quote
Note
The $index parameter for the at() matcher refers to the index, starting at zero, in all method invocations for a given mock object. Exercise caution when using this matcher as it can lead to brittle tests which are too closely tied to specific implementation details.

Categories