【问题标题】:Mocking concrete method in abstract class using phpunit使用phpunit在抽象类中模拟具体方法
【发布时间】:2011-12-23 19:51:57
【问题描述】:

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

到目前为止我发现的是:

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


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

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
    }
}

【问题讨论】:

  • 您不需要测试抽象类,因为它们是抽象的。还是你也想写一个抽象的测试用例?
  • 抽象类是另一个类的依赖。所以我想测试使用 $object->concreteMethod() 的 SomeClass::getMyCalculatedValue()。由于concreteMethod() 可以更改或可能难以设置,我想指定concreteMethod() 的返回值。

标签: php unit-testing mocking phpunit


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    我在我的基本测试用例中覆盖getMock() 以添加所有抽象方法,因为无论如何您必须全部模拟它们。毫无疑问,您可以对 builder 做类似的事情。

    重要提示:您不能模拟私有方法。

    public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE) {
        if ($methods !== null) {
            $methods = array_unique(array_merge($methods, 
                    self::getAbstractMethods($originalClassName, $callAutoload)));
        }
        return parent::getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload);
    }
    
    /**
     * Returns an array containing the names of the abstract methods in <code>$class</code>.
     *
     * @param string $class name of the class
     * @return array zero or more abstract methods names
     */
    public static function getAbstractMethods($class, $autoload=true) {
        $methods = array();
        if (class_exists($class, $autoload) || interface_exists($class, $autoload)) {
            $reflector = new ReflectionClass($class);
            foreach ($reflector->getMethods() as $method) {
                if ($method->isAbstract()) {
                    $methods[] = $method->getName();
                }
            }
        }
        return $methods;
    }
    

    【讨论】:

    • 这与getMockForAbstractClass 有何不同?
    • @FoolishSeth 以前版本的 PHPUnit 不允许您使用 specify additional concrete methods to mock。由于您无法在不模拟每个抽象方法的情况下实例化模拟对象,因此在调用 getMock() 等时将它们合并到列表中似乎是合理的。
    猜你喜欢
    • 2011-04-12
    • 2013-04-16
    • 2020-02-18
    • 2015-03-20
    • 2016-12-05
    • 1970-01-01
    • 2018-10-06
    • 2013-05-16
    • 1970-01-01
    相关资源
    最近更新 更多