使用phpunit在抽象类中模拟具体方法

2022-01-08 00:00:00 mocking unit-testing php phpunit

有没有什么好的方法可以使用 PHPUnit 模拟抽象类中的具体方法?

Are there any good ways to mock concrete methods in abstract classes using PHPUnit?

到目前为止我发现的是:

  • expects()->will() 使用抽象方法可以正常工作
  • 它不适用于具体方法.而是运行原始方法.
  • 使用 mockbuilder 并将所有抽象方法和具体方法提供给 setMethods() 有效.但是,它需要您指定所有抽象方法,这使得测试变得脆弱且过于冗长.
  • MockBuilder::getMockForAbstractClass() 忽略 setMethod().


以下是一些对上述几点进行示例的单元测试:


Here are some unit tests examplifying the above points:

abstract class AbstractClass {
    public function concreteMethod() {
        return $this->abstractMethod();
    }

    public abstract function abstractMethod();
}


class AbstractClassTest extends PHPUnit_Framework_TestCase {
    /**
     * This works for abstract methods.
     */
    public function testAbstractMethod() {
        $stub = $this->getMockForAbstractClass('AbstractClass');
        $stub->expects($this->any())
                ->method('abstractMethod')
                ->will($this->returnValue(2));

        $this->assertSame(2, $stub->concreteMethod()); // Succeeds
    }

    /**
     * Ideally, I would like this to work for concrete methods too.
     */
    public function testConcreteMethod() {
        $stub = $this->getMockForAbstractClass('AbstractClass');
        $stub->expects($this->any())
                ->method('concreteMethod')
                ->will($this->returnValue(2));

        $this->assertSame(2, $stub->concreteMethod()); // Fails, concreteMethod returns NULL
    }

    /**
     * One way to mock the concrete method, is to use the mock builder,
     * and set the methods to mock.
     *
     * The downside of doing it this way, is that all abstract methods
     * must be specified in the setMethods() call. If you add a new abstract
     * method, all your existing unit tests will fail.
     */
    public function testConcreteMethod__mockBuilder_getMock() {
        $stub = $this->getMockBuilder('AbstractClass')
                ->setMethods(array('concreteMethod', 'abstractMethod'))
                ->getMock();
        $stub->expects($this->any())
                ->method('concreteMethod')
                ->will($this->returnValue(2));

        $this->assertSame(2, $stub->concreteMethod()); // Succeeds
    }

    /**
     * Similar to above, but using getMockForAbstractClass().
     * Apparently, setMethods() is ignored by getMockForAbstractClass()
     */
    public function testConcreteMethod__mockBuilder_getMockForAbstractClass() {
        $stub = $this->getMockBuilder('AbstractClass')
                ->setMethods(array('concreteMethod'))
                ->getMockForAbstractClass();
        $stub->expects($this->any())
                ->method('concreteMethod')
                ->will($this->returnValue(2));

        $this->assertSame(2, $stub->concreteMethod()); // Fails, concreteMethod returns NULL
    }
}

推荐答案

2年前有一个Pull Request,但从未在文档中添加信息:https://github.com/sebastianbergmann/phpunit-mock-objects/pull/49

There was a Pull Request for this 2 years ago, but the information never been added in the documentation : https://github.com/sebastianbergmann/phpunit-mock-objects/pull/49

您可以在 getMockForAbstractClass() 的参数 7 中的数组中传递具体方法.

You can pass your concrete method in an array in argument 7 of getMockForAbstractClass().

查看代码:https://github.com/andreaswolf/phpunit-mock-objects/blob/30ee7452caaa09c46421379861b4128ef7d95e2f/PHPUnit/Framework/MockObject/Generator.php#L225

相关文章