PHPUnit:模拟后包含类 [英] PHPUnit: include class after mocking it
问题描述
我正在愉快地编写单元测试,但是当我一起运行它们时它们会发生冲突.我正在测试这堂课:
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
可以正常运行.但是,如果另一个测试模拟了MyClass
并且恰好先运行,则PHPUnit将崩溃,因为我重新声明了该类.
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());
}
}
我确实使用bootstrap.php伪造了一些尚未重构的全局函数.
I do use a bootstrap.php to fake some global functions not yet refactored.
我没有自动加载程序,也不想对每个测试进行过程隔离,因为它会花费很多时间.我尝试将组合@runTestsInSeparateProcesses
和@preserveGlobalState enabled
/disabled插入到Test 1& 2,我仍然遇到相同的错误.
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.
推荐答案
要了解此行为,您需要了解PHPUnit的工作方式. getMockBuilder()->getMock()
,为模拟类动态创建以下代码:
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 ...
如果此时尚未加载MyClass
,它将添加以下伪声明:
If MyClass
hasn't already been loaded at this time, it adds the following dummy declaration:
class MyClass
{
}
然后将使用eval()
(!)解析此代码.
This code will then getting parsed using eval()
(!).
由于PHPUnit将在InjectTest
之前 MyClassTest
之前执行,因此这意味着模拟生成器将定义虚拟类,并且在调用MyClassTest::setUpBeforeClass
时已经定义了MyClass
.这就是错误的原因.我希望我能解释.否则,请深入研究PHPUnit的代码.
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.
解决方案:
拖放setUpBeforeClass()
方法并将require_once
语句放在测试之上. setUpBeforeClass()
并不意味着包含类.请参考文档.
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.
顺便说一句,将require_once
放在最前面将起作用,因为PHPUnit将在开始第一个测试之前之前包含每个测试文件.
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());
}
}
这篇关于PHPUnit:模拟后包含类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!