Создание объекта mock со свойствами по умолчанию

Мне нужно создать mock-объект со стандартным набором свойств, чтобы его можно было использовать elseware в базе кода после создания экземпляра.

$mock = $this->getMock('MyClass', array(), array(), 'MyClass_Mock');
$mock->prop = 'foobar';

$myclassMock = new get_class($mock);
var_dump($myclassMock->prop); // NULL
// How can I make this dump 'foobar' ?

Я тестирую часть фреймворка, которая определяет, находит и создает экземпляры этих классов, поэтому вводящий насмешливый объект победит цель теста.

Мне не нужно издеваться над любыми методами.. просто динамически создавайте классный класс:

class MyClass_Mock extends MyClass {
  public $prop = 'foobar';
}

Изменить: упрощенный пример

Ответ 1

Как вы относитесь к использованию Reflection?

$r = new ReflectionClass('MyClass');

$props = $r->getDefaultProperties();

$mock = new stdClass;

foreach ($props as $prop => $value) {
    $mock->$prop = $value;
}

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

Edit:

Отсканированные функции отражения из любопытства, вполне возможно полностью имитировать класс с помощью фиктивных методов, реализуя полные ограничения видимости, константы и статические элементы, когда это необходимо, если вы динамически создаете класс в строке и eval Это.

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

Желаем удачи, если вы спуститесь по этому маршруту, это потребует большей мощности мозга, чем я готов пощадить прямо сейчас:)

Здесь немного кода, вы можете сделать то же самое с константами и создать пустые методы аналогичным образом.

class Test
{
    private static $privates = 'priv';
    protected $protected = 'prot';
    public $public = 'pub';
}

$r = new ReflectionClass('Test');

$props = $r->getDefaultProperties();

$mock = 'class MockTest {';

foreach ($props as $prop => $value) {
    $rProp = $r->getProperty($prop);


    if ($rProp->isPrivate()) {
        $mock .= 'private ';
    }
    elseif ($rProp->isProtected()) {
        $mock .= 'protected ';
    }
    elseif ($rProp->isPublic()) {
        $mock .= 'public ';
    }

    if ($rProp->isStatic()) {
        $mock .= 'static ';
    }

    $mock .= "$$prop = ";

    switch (gettype($value)) {
        case "boolean":
        case "integer":
        case "double":
            $mock .= $value;
            break;
        case "string":
            $mock .= "'$value'";
            break;
/*
"array"
"object"
"resource"
*/
    case "NULL":
            $mock .= 'null';
            break;
    }

    $mock .= ';';
}

$mock .= '}';

eval($mock);

var_dump(new MockTest);

Ответ 2

Я не уверен, что вы даже нуждаетесь, чтобы сделать это для целей тестирования.

Обычно при тестировании кода, который включает в себя доступ к модели, вы используете приспособления вместо того, чтобы издеваться над фактическими моделями, потому что модели являются "немыми" структурами данных, которые не раскрывают никаких возможностей, которые необходимо издеваться.

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

Конечно, если вы решите, что хотите все равно издеваться над моделью, я бы предложил решение @Leigh для отражения.

Я только что ответил на вопрос о тестировании базы данных вчера, что вы можете проверить немного подробнее: PHPUnit: как протестировать взаимодействие с базами данных на удаленном сервере Postgres?

Ответ 3

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

Если это так, вы можете создать простой базовый класс для заполнения предопределенного набора свойств при построении. Решение ниже использует позднюю статическую привязку из PHP 5.3, но вы можете легко достичь тех же результатов без него с небольшой настройкой.

class MockModel
{
    public static $properties;

    public function __construct() {
        if (isset(static::$properties) && is_array(static::$properties)) {
            foreach (static::$properties as $key => $value) {
                $this->$key = $value;
            }
        }
    }
}

class MockBook extends MockModel { /* nothing needed */ }

function testBookWithTitle() {
    MockBook::$properties = array(
        'title' => 'To Kill a Mockingbird'
    );
    $book = new MockBook;
    self::assertEquals('To Kill a Mockingbird', $book->title);
}

Пока вы можете предоставить имя класса, которое будет использоваться с new, в вашу инфраструктуру, это должно сработать. Если вам нужно создать более одного экземпляра одного и того же макетного класса во время одного вызова вашей фреймворка, вам необходимо усилить это с помощью какого-то механизма индексирования.