I'm happily writing unit tests, but they clash when I run them all together. I'm testing this class:
class MyClass {
public function sayHello() {
return 'Hello world';
}
}
using this test. All tests have a structure like this:
class MyClassTest extends PHPUnit_Framework_TestCase {
private $subject;
public static function setUpBeforeClass() {
require_once('path/to/MyClass.php');
}
public function setUp() {
$this->subject = new MyClass();
}
public function testSayHello() {
$this->assertEquals('Hello world', $this->subject->sayHello());
}
}
MyClassTest
runs fine on its own. But PHPUnit will crash because I redeclare the class, if another test mocks MyClass
and happens to run first:
class Inject {
private $dependency;
public function __construct(MyClass $myClass) {
$this->dependency = $myClass;
}
public function printGreetings() {
return $this->dependency->sayHello();
}
}
class InjectTest extends PHPUnit_Framework_TestCase {
public function testPrintGreetings() {
$myClassMock = $this
->getMockBuilder('MyClass')
->setMethods(array('sayHello'))
->getMock();
$myClassMock
->expects($this->once())
->method('sayHello')
->will($this->returnValue(TRUE));
$subject = new Inject($myClassMock);
$this->assertEquals('Hello world', $subject->printGreetings());
}
}
I do use a bootstrap.php to fake some global functions not yet refactored.
I have no auto loaders and don't want to process-isolate EVERY test, because it takes forever. I tried inserting combinations @runTestsInSeparateProcesses
and @preserveGlobalState enabled
/disabled in the docblocks of both Test 1 & 2, I still get the same error.
To understand this behaviour, you need to have a look at how PHPUnit works. getMockBuilder()->getMock()
, dynamically creates the following code for the mock class:
class Mock_MyClass_2568ab4c extends MyClass implements PHPUnit_Framework_MockObject_MockObject
{
private static $__phpunit_staticInvocationMocker;
private $__phpunit_invocationMocker;
public function __clone()
{
$this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationMocker();
}
public function sayHello()
{
$arguments = array();
$count = func_num_args();
if ($count > 0) {
$_arguments = func_get_ ...
# more lines follow ...
If MyClass
hasn't already been loaded at this time, it adds the following dummy declaration:
class MyClass
{
}
This code will then getting parsed using eval()
(!).
Since PHPUnit will execute InjectTest
before MyClassTest
, this means that the mock builder will define the dummy class and MyClass
is already defined when MyClassTest::setUpBeforeClass
will get called. That's why the error. I hope I could explain. Otherwise, dig into PHPUnit's code.
Solution:
Drop the setUpBeforeClass()
method and put the require_once
statement on top of the tests. setUpBeforeClass()
is not meant for including classes. Refer to the docs.
Btw, having require_once
on top will work because PHPUnit will include every test file before starting the first test.
tests/MyClassTest.php
require_once __DIR__ . '/../src/MyClass.php';
class MyClassTest extends PHPUnit_Framework_TestCase {
private $subject;
public function setUp() {
$this->subject = new MyClass();
}
public function testSayHello() {
$this->assertEquals('Hello world', $this->subject->sayHello());
}
}
tests/InjectTest.php
require_once __DIR__ . '/../src/Inject.php';
class InjectTest extends PHPUnit_Framework_TestCase {
public function testPrintGreetings() {
$myClassMock = $this
->getMockBuilder('MyClass')
->setMethods(array('sayHello'))
->getMock();
$myClassMock
->expects($this->once())
->method('sayHello')
->will($this->returnValue(TRUE));
$subject = new Inject($myClassMock);
$this->assertEquals(TRUE, $subject->printGreetings());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With