简体   繁体   中英

'Cannot redeclare class' error when trying to mock out dependencies in PHPUnit

I'm implementing unit testing with PHPUnit on an existing codebase. I'm fairly new to unit testing, but I understand that a goal is to completely isolate the code one is testing. This is proving difficult with my codebase because many of the classes depend on other classes in the codebase.

The dependencies are hard coded into the classes, so there's no way to use dependency injection. I also don't want to refactor the existing code just for the sake of testing it. So in order to isolate each class from it's dependencies, I've created a library of "mock" classes (not by using PHPUnit's mocking framework, but by literally creating a library of classes that contain stub functions that return what I expect based on specific input).

The problem is that if, during a run of phpunit, I have a test that calls the mock class, and then I try to test the actual class, I get a fatal error because PHP sees this as redeclaring the class. Here is a simplified example of what I mean. Note that this still fails even if I unset all instances of the classes I've included and clear the include path in a tearDown method. Again, I'm new to unit testing, so if I'm going about this the wrong way or missing something obvious, please let me know.

A bigger question could be if this approach is going to far in the direction of isolating code, and if it's actually a benefit to use real objects as my class' dependencies.

#### real A

require_once 'b.class.php';

class A {   
    private $b;

    public function __construct() {
        $this->b = new B();
    }

    public function myMethod($foo) {
        return $this->b->exampleMethod($foo);
    }
}


#### real B

class B {
    public function exampleMethod($foo) {
        return $foo . "bar";
    }
}


#### mock B

class B {
    public function exampleMethod($foo) {
        switch($foo) {
            case 'test':
                return 'testbar';
            default:
                throw new Exception('Unexpected input for stub function ' . __FUNCTION__);
        }
    }
}


#### test A

class TestA extends PHPUnit_Extensions_Database_TestCase {
    protected function setUp() 
    {
        // include mocks specific to this test
        set_include_path(get_include_path() . PATH_SEPARATOR . 'tests/A/mocks');

        // require the class we are testing
        require_once 'a.class.php';     

        $this->a = new A();
    }

    public function testMyMethod() {
        $this->assertEquals('testbar', $a->myMethod('test'));
    }
}

#### test B

class TestB extends PHPUnit_Extensions_Database_TestCase {
    protected function setUp()
    {
        // include mocks specific to this test
        set_include_path(get_include_path() . PATH_SEPARATOR . 'tests/B/mocks');

        // require the class we are testing
        // THIS FAILS WITH: 'PHP Fatal error:  Cannot redeclare class B'
        require_once 'b.class.php';

        $this->b = new AB();
    }   
}

I think you need to run your tests in isolated processes

Either you give an argument for executing your test: "--process-isolation" or you set $this->processIsolation = true;

If you, for some reason (and there are some valid ones) can't use the PHPUnit mocking API (or Mockery) then you need to create the mock classes your self.

A mocked class should have the same "Type" as the real one (so that type hinting still works) and for that reason you should extend the real one:

#### mock B

class Mock_B extends B {

This also works around the fact that you can't have 2 classes with the same name in PHP :)

您还可以在声明模拟时使用名称空间

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM