I am learning unit testing with PHPUnit and am running into a strange problem with mock objects that I can't resolve. I set up these dummy files as an example of what I am experiencing:
Class1
class PrimaryObj1
{
function doNothing(SecondObj $s)
{
}
}
Class2
class PrimaryObj2
{
function doNothing(SecondObj $s)
{
$s->TestMethod();
}
}
and a test file as:
class PrimaryObj1_test extends PHPUnit_Framework_TestCase
{
private $primaryObj;
function setUp()
{
$this->primaryObj = new PrimaryObj1();
}
function testDoNothing()
{
$secondObj = $this->getMock('SecondObj');
$secondObj->expects($this->once())
->method("TestMethod");
$this->primaryObj->doNothing($secondObj);
}
}
(one test file for each dummy class where everything is the same except for the class name).
When I run PHPUnit, this is what I get:
Running Tests/PrimaryObj1_test.php
1) PrimaryObj1_test::testDoNothing
Expectation failed for method name is equal to <string:TestMethod> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Running Tests/PrimaryObj2_test.php
Fatal error: Call to undefined method Mock_SecondObj_99c898e7::TestMethod() in PrimaryObj2.php on line 5
So first it is mad that it didn't call the expected method but then when I do it gets mad cause it is undefined. I just can't win. I think this is why I'm still single...
Any thoughts on why this might be happening?
I got a response from an email list serve with the answer. This line:
$secondObj = $this->getMock('SecondObj');
should be:
$secondObj = $this->getMock('SecondObj', array('TestMethod'));
Once I made that change it worked as expected.
Related
I'm trying to test if a class is final. Since I've not found a default matcher for this (or any other clean way of testing it), I decided to create a custom extension which adds a new matcher to do just that, but I can't get it to work.
I've tried it with an inline matcher, like so:
public function getMatchers(): array
{
return [
'beFinal' => function ($subject) {
$reflection = new \ReflectionClass($subject);
if (!$reflection->isFinal()) {
throw new FailureException('Expected subject to be final, but it is not.');
}
return true;
},
];
}
This works well enough when I call $this->shouldBeFinal();. The problem is that when I call $this->shouldNotBeFinal();, it outputs a generic message: [obj:Class\To\Test] not expected to beFinal(), but it did., instead of one I'd like to show.
Another problem is that I don't want this for just one class. That's why I decided to make an extension for it.
Here's what I got:
phpspec.yml:
extensions:
PhpSpecMatchers\Extension: ~
PhpSpecMatchers/Extension.php:
<?php
declare(strict_types=1);
namespace PhpSpecMatchers;
use PhpSpec\ServiceContainer;
use PhpSpecMatchers\Matchers\BeFinalMatcher;
class Extension implements \PhpSpec\Extension
{
public function load(ServiceContainer $container, array $params): void
{
$container->define(
'php_spec_matchers.matchers.be_final',
function ($c) {
return new BeFinalMatcher();
},
['matchers']
);
}
}
PhpSpecMatchers/Matchers/BeFinalMatcher.php:
<?php
declare(strict_types=1);
namespace PhpSpecMatchers\Matchers;
use PhpSpec\Exception\Example\FailureException;
use PhpSpec\Matcher\BasicMatcher;
class BeFinalMatcher extends BasicMatcher
{
public function supports(string $name, $subject, array $arguments): bool
{
return $name === 'beFinal';
}
public function getPriority(): int
{
return 0;
}
protected function matches($subject, array $arguments): bool
{
$reflection = new \ReflectionClass($subject);
return $reflection->isFinal();
}
protected function getFailureException(string $name, $subject, array $arguments): FailureException
{
return new FailureException('Expected subject to not be final, but it is.');
}
protected function getNegativeFailureException(string $name, $subject, array $arguments): FailureException
{
return new FailureException('Expected subject to be final, but it is not.');
}
}
Whenever I try to call $this->beFinal(); with this configuration, the spec is broken and shows the following message: method [array:2] not found.. If I add an isFinal() method to the class I'm testing and return true for example, it passes for $this->shouldBeFinal(); and fails for $this->shouldNotBeFinal();, but I don't want to add that method. I should just work without it and for as far as I understand it should be able to work like that, right?
I've also tried adding custom suites to my phpspec.yml, like so:
suites:
matchers:
namespace: PhpSpecMatchers\Matchers
psr4_prefix: PhpSpecMatchers\Matchers
src_path: src/PhpSpecMatchers/Matchers
spec_prefix: spec/PhpSpecMathcers/Matchers
But that doesn't change anything. I've also tried to add the following config to phpspec.yml:
extensions:
PhpSpecMatchers\Extension:
php_spec_matchers:
src_path: src
spec_path: spec
That also doesn't change anything.
One other thing I've tried was to ditch the extension approach and just declare my mather in phpspec.yml, like so:
matchers:
- PhpSpecMatchers\Matchers\BeFinalMatcher
As you might expect: same results.
The loading in PhpSpecMatchers\Extension does get called (tested by a simple var_dump(…);), but it doesn't seem to reach anything within PhpSpecMatchers\Matchers\BeFinalMatcher, since I don't get any output from any var_dump(…);
I've followed tutorials and examples from symfonycasts, phpspec docs itself and some other github project I found, they're all almost identically to my code (except namespaces, directory structures and stuff like that), so I'm kind of at a loss here.
How do I get it to a point where I can successfully call $this->shouldBeFinal(); and $this->shouldNotBeFinal();?
Many thanks to whovere can help me out here.
P.S.: I've also posted this issue on phpspec's github.
So apparently the priority was too low (see this comment my phpspec's github issue). PhpSpec\Matcher\IdentityMatcher (where the shouldBe comes from) extends from PhpSpec\Matcher\BasicMatcher where the priority is set to 100.
Since mine was set to 0 it got to mine first (I think) and therefore didn't execute properly. I've set my priority to 101 and it works flawlessly (except I had switched the positive and negative messages, I found out).
I am currently using phpunit for some unit testing. Due to the presence of some protected methods, I had to use a Reflection Class to change the visibility of these methods to public.
The initial methods were called successfully but somehow it gets stuck at a specific method:
Fatal error: Call to undefined method ReflectionClass::create_schema()in
/vagrant/fuelphp/fuel/app/tests/model/repository/jobpost.php on line 54
However, dumping the method via get_method() with var_dump proves that it exists in the class instance:
class ReflectionMethod#2317 (2) {
public $name =>
string(13) 'create_schema'
public $class =>
string(34) 'Model_Repository_Feed'
}
Then the real confusing bit, I decided to use hasMethod() to see if the method exists:
52 echo "If this says 1, class exists: ".$this->_target->hasMethod('create_schema');
53 try {
54 $this->_target->create_schema();
55 }
The result when running says, "yes it exists.... but it doesn't":
If this says 1, class exists: 1
Fatal error: Call to undefined method ReflectionClass::create_schema() in /vagrant/fuelphp/fuel/app/tests/model/repository/jobpost.php on line 54
To clarify this method is public and is inherited from an abstract parent class:
public function create_schema() {
$this->create_schema_exec(self::$_real_schema_name);
}
How can this issue be solved?
you need to get the object of the class that holds the method not the reflection object.
$reflection = new ReflectionClass($className);
$object = $reflection->newInstanceWithoutConstructor();
$object->methodName();
I've been busy trying to create my own framework (to become more experienced in this area), and stumbled on an error I couldn't fix by searching google... wow...
I want to get data from a database, placed in an object / class. I've done it before, in a different way I learned at school, but I wanted to tweak it and make it more dynamic so I could use it in my framework.
The problem I stumbled on is the following:
SQLSTATE[HY000]: General error: could not call class constructor on line 96
This is the function in my database class:
public function getObject($query, $classRootPath)
{
try {
//Check if slashes are double already and make them if not
if(!strpos($classRootPath, "\\\\")) {
$classRootPath = str_replace("\\","\\\\",$classRootPath);
}
$statement = $this->pdo->prepare($query);
$statement->execute(\PDO::FETCH_CLASS, "Campers\\Camper"); // I want this path to be $classRootPath once it is working with this dummy data
return $statement->fetchAll();
// return $this->pdo->query($query)->fetchAll(\PDO::FETCH_CLASS, "Campers\\Camper");
} catch (\PDOException $e) {
throw new \Exception("DB receive object failed: " . $e->getMessage());
}
}
This function is nested in Database and the class is called Database / Database.php
The following class is nested in Campers and is called Camper.php
class Camper {
public $ID, $date, $camperID;
public function __construct($ID, $date, $camperID)
{
$this->ID = $ID;
$this->date = $date;
$this->camperID = $camperID;
}
}
The only reason I can think of this is not working, is that the call "Campers\\Camper" is calling on top of Database, but I don't know how to escape that. I tried with ..\ but I got errors back, and this is the closest I can get. Here it can find the class though, but it can't find the constructor of Camper...
I've tested if my db class / connection works, so that's not the fault.
The structure of my table matches my Campers class constructor.
From the PSR-4 spec:
The terminating class name corresponds to a file name ending in .php. The file name MUST match the case of the terminating class name.
You likely can't instantiate that Camper class as-is anyway. PSR-4 expects your filename to match the class. It should be located in framework/Campers/Camper.php.
This error implies more than been unable to call the constructor, it is also used to indicate than an error occurred while calling it.
In my case, an Exception was been thrown inside de constructor. If you don't print/log the stacktrace, you could easily miss it.
Enjoy!
:)
I had the same issue in at least 3 cases.
Case 1: You select something from the database that can contain a NULL value.
SELECT name FROM tableX;
In that case I do the select in that way:
SELECT IFNULL(name,'') AS name FROM tableX;
where name is a field in your class.
Case 2: You select something that is not a field in your class
class Example {
public string $name = '';
}
Then the following query will fail as id is not declared in your class
SELECT id, name FROM tableX;
case3:
your field in the class isn't initialised
class Example {
public string $name;
}
SELECT name FROM tableX;
can be solved by either initialise the field
class Example {
public string $name = '';
}
or using a constructor to declare it
BR
I'm trying to use setData in my Address object.
Here is the code:
//code inside another class
$this->getAddress()->setData('abc', 'abc')->collectShippingRates()->save();
class Mage_Sales_Model_Quote_Address extends Mage_Customer_Model_Address_Abstract
{
...
public function collectShippingRates()
{
...
$found = $this->requestShippingRates();
...
return $this;
}
public function requestShippingRates(Mage_Sales_Model_Quote_Item_Abstract $item = null)
{
//I knkow it's getting here, because when I echo 'blablabla', it works.
//When I use $this->setData('abc', 'abc') here, the code bellow print 'abc'
//The problem is when I use setData outside the class
echo $this->getData('abc');exit; //prints nothing
...
}
...
}
As you can see, I setData in my Address model, but inside the class getData returns nothing.
Can someone please explain what's happening?
When I try to use Mage::register, it does not work too.
Possible avenues of exploration.
Your call to getAddress isn't returning a Mage_Sales_Model_Quote_Address object, or is returning a different instance of an address object.
Your call to setData('abc', 'abc') happens later in Magento's code execution than the first call to requestShippingRates.
Code between your call to getAddress and the execution of requestShippingRates is re-instantiating or initializing the address object.
Based on your comments about Mage::register, my money is on number two.
If I have this code, the string "test" is echoed. This is in PHP 5.3. Is this some oversight that shouldn't be relied on, or is it some way of achieving multiple inheritence in PHP?
class Test1
{
function getName()
{
return $this->name;
}
}
class Test2
{
public $name = 'test';
function getName()
{
return Test1::getName();
}
}
$test = new Test2;
echo $test->getName();
EDIT:
As has been pointed out the comments by GZipp this is actually documented behaviour. See this page: http://us2.php.net/manual/en/language.oop5.basic.php and the heading "Example #2 Some examples of the $this pseudo-variable".
Classes A and B have a similar relationship to my two test classes above and the lines
$b = new B();
$b->bar();
Show more or less the same result as my example.
Just to be clear - this isn't inheritance. Test2 does not extend Test1. You statically referenced a public method of the Test1 class.
Nonetheless, the fact that it returns 'test' is interesting to say the least. And I see where it is giving off the idea of inheritance.
If you can't find a decent answer, I'd submit your code as a bug.
UPDATE
It looks like under the hood, even though you statically referenced a method of Test1, it's still being called as Test2. In the end, this is undefined behavior, and ask noted above does throw a strict warning. Still very odd, and I personally agree that it shouldn't work. But just to shed a little more insight which object it is using.
class Test1 {
function getName() {
echo get_class($this);
return $this->name;
}
}
// ...
$test = new Test2;
echo $test->getName();
// echoes 'Test2 test'
PHP allows calling non-static methods as if they were static - that's a feature. PHP passes $this as an implicit parameter to such calls. Much like it does when calling a method the normal way.
But obviously PHP doesn't check whether the statically called class inherits the current one - and that's the bug.
This is how you could think of what PHP does:
function Test1->getName($this = null) {
return $this->name;
}
function Test2->getName($this = null) {
return Test1->getName($this);
}
$test = new Test2;
echo $test->getName($test);
This behavior is wrong. The correct Test2->getName would be:
function Test2->getName($this = null) {
return $this instanceof Test1 ? Test1->getName($this) : Test1->getName();
}
This has to be a bug. 100%.
This is not inheritance.
You should not be able to call
Test1::getName() as getName() in
Test1 is not static
Even if you
could call it, $this->name should
not have access to the value in
Test2.
This breaks so many rules of OO I am seriously baffled how this got through testing. Well don for finding it!!