Насмешливый конкретный метод в абстрактном классе с использованием phpunit - PullRequest
15 голосов
/ 07 ноября 2011

Существуют ли хорошие способы для имитации конкретных методов в абстрактных классах с использованием PHPUnit?

На данный момент я нашел:

  • Ожидает () -> будет () отлично работает с использованием абстрактных методов
  • Не работает для конкретных методов. Вместо этого запускается оригинальный метод.
  • Использование 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
    }
}

Ответы [ 2 ]

15 голосов
/ 13 ноября 2012

2 года назад был запрос на извлечение информации, но эта информация никогда не добавлялась в документацию: https://github.com/sebastianbergmann/phpunit-mock-objects/pull/49

Вы можете передать ваш конкретный метод в массиве в аргументе 7 метода getMockForAbstractClass ().

См. Код: https://github.com/andreaswolf/phpunit-mock-objects/blob/30ee7452caaa09c46421379861b4128ef7d95e2f/PHPUnit/Framework/MockObject/Generator.php#L225

4 голосов
/ 08 ноября 2011

Я переопределяю getMock() в моем базовом тестовом примере, чтобы добавить все абстрактные методы, потому что вы должны все равно высмеивать их все. Вы можете сделать что-то подобное со строителем, без сомнения.

Важно: Нельзя издеваться над личными методами.

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