PHPUnit превращает экземпляр класса в макет после создания экземпляра

Есть ли способ создать класс mock с PHPUnit, который затем я могу создать новый экземпляр с помощью его имени класса?

У меня есть интерфейс, который определяет два метода. Что-то вроде:

interface FooInterface {
    function getA();
    function getB();
}

Затем у меня есть другой класс, который принимает имя класса, создает экземпляр этого класса, проверяет, является ли он экземпляром ожидаемого (FooInterface), а затем вызывает два метода в этом классе, чтобы получить некоторую информацию.

class FooInfo {
    protected $a;
    protected $b;

    public function __construct($fooClass) {
        $foo = new $fooClass;

        if (!($foo instanceof FooInterface)) {
            throw new \Exception();
        }

        $this->a = $foo->getA();
        $this->b = $foo->getB();
    }
}

Я знаю, как издеваться над объектом просто отлично. Проблема в том, что, поскольку этот класс принимает имя класса, а не объект (он является частью Менеджера, который создает экземпляры данного класса по мере необходимости), я не могу использовать обычный макет.

Я попытался сделать mock-объект, а затем использовать это имя класса. Кажется, он создает объект просто отлично, и даже кажется, что у меня есть функции, которые я издевался. Тем не менее, он, похоже, не соответствует завещанию ($ this- > returnValue ('myValue')), которое я установил позже.

public function testConstruct()
{
    $foo = $this->getMockForAbstractClass('Foo', array('getA', 'getB'));
    $foo->expects($this->any())->method->('getA')->will($this->returnValue('a'));
    $foo->expects($this->any())->method->('getB')->will($this->returnValue('b'));

    $copyClass = get_class($foo);
    $copy = new $copyClass();

    // Passes
    $this->assertTrue(method_exists($copy, 'getA');

    // Fails, $copy->getA() returns null.
    $this->assertEquals($copy->getA(), $foo->getA());
}

Итак, у него есть функции, которые были издевались, но все они возвращают null.

Любые идеи?

Ответ 1

Использование ключевого слова new в конструкторе класса является довольно дурной привычкой именно по причинам, которые вы испытываете сейчас, даже учитывая ваш гибкий вариант использования.

Ваш тест не будет работать, потому что созданный вами макет никогда не будет использоваться, так как ваш класс всегда будет создавать реальный экземпляр введенного имени класса.

Тем не менее, то, что вы хотите сделать, можно сделать с помощью padraic отличной насмешливой библиотеки mockery! Что вам нужно, это "пример макета":

$mock = \Mockery::mock('overload:MyNamespace\MyClass');

Вы можете определить свои ожидания по этому макету, который будет передан реальному объекту, как только он будет создан.

Интегрирование издевательства с phpUnit легко и хорошо объясняется в readme проекта.

И кстати. каждый unit test должен оптимально сделать одно утверждение только!

Ответ 2

Методы getA и getB возвращают значение null, потому что вы не указали, что они должны вернуть. Вы указали это для абстрактного $foo, вызвав некоторые методы. Ничего подобного.

Поскольку это трудно (если не невозможно) для проверки функции, я бы переписал сам код. Тестирование было бы легко, если бы вашему конструктору потребовался экземпляр класса, а не имя класса. Если вы также должны принимать строки, вы можете написать несколько строк, чтобы проверить ввод строки и создать класс, если строка предоставлена.