PHPUnit: enable a class after mocking it

I happily write unit tests, but they collide when I run them all together. I am testing this class:

class MyClass { public function sayHello() { return 'Hello world'; } } 

using this test. All tests have the following structure:

 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 works MyClassTest fine. But PHPUnit will fail because I am updating the class if another test mocks MyClass 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 use bootstrap.php to fake some global functions that are not yet reorganized.

I do not have autoloaders and do not want to process-isolate EVERY test because it takes forever. I tried to insert combinations of @runTestsInSeparateProcesses and @preserveGlobalState enabled / disabled in the doc blocks of both tests 1 and 2, I still get the same error.

+5
source share
1 answer

To understand this behavior, you need to see 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 is not already loaded at this time, it adds the following declaration:

 class MyClass { } 

This code will be processed using eval() (!).

Since PHPUnit will execute an InjectTest before MyClassTest , this means that the builder layout will define a dummy class, and MyClass already defined when MyClassTest::setUpBeforeClass is called. That is why the error. Hope I could explain. Otherwise, dig out the PHPUnit code.


Decision:

Drop the setUpBeforeClass() method and put the require_once statement on top of the tests. setUpBeforeClass() not intended to include 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()); } } 
+5
source

Source: https://habr.com/ru/post/1201942/


All Articles