Как установить аргументы объекта вызова функции фиктивного объекта - PullRequest
11 голосов
/ 11 мая 2011

Рассмотрим

class Foo {
    public $att;
    public function __construct ( $a ) { $this->att = $a; }
}

class Some {
    public function callMe ( Foo $f ) {}
}

// class I want to test
class SuT {
    public function testMe ( Some $s ) {
        echo $s->callMe( new Foo('hi') );
    }
}

Я хочу проверить, правильно ли Sut::testMe() вызывает Some::callMe(). Поскольку параметр является (Foo) объектом (не скалярным типом), я не могу понять, как вызвать PHPUnit with() для выполнения утверждений на нем. Например, есть метод assertAttributeEquals, но как мне передать ему аргумент вызова?

То, что я хотел бы сделать, это:

class SuTTest extends PHPUnit_Framework_TestCase {
    public function testSuT () {
        $stub = $this->getMock( 'Some' );
        $stub->expects( $this->once() )->method( 'callMe' )
            ->with( $this->assertAttributeEquals('hi', 'att', $this->argument(0) );

        /*
         * Will $stub->callMe be called with a Foo object whose $att is 'hi'?
         */
        $sut = new SuT();
        $sut->testMe( $stub );
    }
}

Ответы [ 3 ]

10 голосов
/ 04 ноября 2013

Несмотря на то, что на этот вопрос уже дан полный ответ (как установить атрибут объекта, переданного в качестве аргумента для mock), я думаю, стоит отметить, что PHPUnit поддерживает ограничение обратного вызова, которое передается с помощью ().

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

К счастью, PHPUnit поддерживает (как минимум 3.7) ограничение обратного вызова, которое имеет большой смысл и его можно найти в специализированных библиотеках Mock, таких как Mockery .

Текущая Версия PHPUnit docs заявляет следующее:

Ограничение callback () может использоваться для более сложной проверки аргументов. Это ограничение принимает обратный вызов PHP в качестве единственного аргумента. Обратный вызов PHP получит аргумент, подлежащий проверке, в качестве единственного аргумента и должен вернуть TRUE, если аргумент проходит проверку, и FALSE в противном случае.

Поэтому, используя ограничение обратного вызова, пример OP теперь можно выразить как:

class SuTTest extends PHPUnit_Framework_TestCase {
    public function testSuT () {
        $stub = $this->getMock('Some');
        $stub->expects($this->once())
            ->method('callMe')
            ->with($this->callback(function($arg) {
                    return ($arg instanceof Some) && ($arg->att === 'hi');
                })
            );

        /*
         * Will $stub->callMe be called with a Foo object whose $att is 'hi'?
         */
        $sut = new SuT();
        $sut->testMe($stub);
    }
}

И провал теста будет выглядеть примерно так:

1) SuTTest :: testSuT
Ожидание не удалось для имени метода равно при вызове 1 раз (а)
Параметр 0 для вызова Some :: callMe (Foo Object (...)) не соответствует ожидаемому значению.
Не удалось утверждать, что Foo Object () принят указанным обратным вызовом.

Теперь вы можете использовать любую логику для этого аргумента.

Еще лучше, несмотря на то, что это не документированная функция в документах PHPUnit, вы можете использовать утверждения и получать преимущества от сообщений об ошибках утверждений:

class SuTTest extends PHPUnit_Framework_TestCase {
    public function testSuT () {
        // alias to circumvent php closure lexical variable restriction
        $test = $this;
        // proceed as normal
        $stub = $this->getMock('Some');
        $stub->expects($this->once())
            ->method('callMe')
            // inject the test case in the closure
            ->with($this->callback(function($arg) use ($test) {
                    // use test assertions
                    $test->assertInstanceOf('Some', $arg);
                    $test->assertAttributeEquals('hi', 'att', $arg);
                    // return true to satisfy constraint if all assertions passed
                    return true;
                })
            );

        /*
         * Will $stub->callMe be called with a Foo object whose $att is 'hi'?
         */
        $sut = new SuT();
        $sut->testMe( $stub );
    }
}

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

1) SuTTest :: testSuT
Ожидание не удалось для имени метода равно при вызове 1 раз (а)
Не удалось утверждать, что две строки равны.
--- Ожидаемый
+++ Фактический
@@ @@
-'hi '
+ 'нет'

Надеюсь, это поможет любому, кто сталкивается с подобной проблемой.

8 голосов
/ 11 мая 2011

Вы просто передаете ожидаемые значения методу «with».

->with(1, $object, "paramThree");

Вы также можете передать диапазон утверждений phpUnit вместо параметров (по умолчанию он равен)

->with(1, $this->equalTo($object), "paramThree");

поэтому для объектов вы должны использовать $this->isInstanceOf("stdClass") в качестве параметра для ->with

Список возможных утверждений можно найти в: PHPUnit/Framework/Assert.php

для функций, которые возвращают new PHPUnit_Framework_Constraint


Small Demo

Первый тестовый случай просто соответствует 2 аргументам и работает

Второйсовпадает с двумя и завершается неудачно с аргументом 2

Последний проверяет, что переданный объект имеет тип stdClass

<?php

class MockMe {
    public function bla() {

    }
}

class Demo {

    public function foo(MockMe $x) {
        $x->bla(1, 2);
    }

    public function bar(MockMe $x) {
        $x->bla(1, new stdClass());
    }

}

class DemoTest extends PHPUnit_Framework_TestCase {

    public function testWorks() {
        $x = new Demo();
        $mock = $this->getMock("MockMe");
        $mock->expects($this->once())->method("bla")->with(1,2);
        $x->foo($mock);
    }

    public function testFails() {
        $x = new Demo();
        $mock = $this->getMock("MockMe");
        $mock->expects($this->once())->method("bla")->with(1,3);
        $x->foo($mock);
    }

    public function testObject() {
        $x = new Demo();
        $mock = $this->getMock("MockMe");
        $mock->expects($this->once())->method("bla")->with(1, $this->isInstanceOf("stdClass"));
        $x->bar($mock);
    }
}

Результат:

phpunit DemoTest.php
PHPUnit 3.5.13 by Sebastian Bergmann.

.F.

Time: 0 seconds, Memory: 4.25Mb

There was 1 failure:

1) DemoTest::testFails
Failed asserting that <integer:2> matches expected <integer:3>.

...DemoTest.php:12
...DemoTest.php:34

FAILURES!
Tests: 3, Assertions: 2, Failures: 1.
7 голосов
/ 20 мая 2011

Это простой пример, который делает то, что вам нужно:

$mock->expects ($this->once())
     ->method ('dummyFunction')
     ->with ($this->logicalAnd ($this->isInstanceOf ('DummyClass')
                               ,$this->attributeEqualTo ('attribute1', 1001)
                               ,$this->attributeEqualTo ('attribute2', 200)))
     ->will ($this->returnValue (null));

А остальной код:

class DummyClass { 
   private $attribute1 = 1001;
   private $attribute2 = 200;
} 
function dummyFunction (DummyClass $p) {...}
dummyFunction (new DummyClass());

Я надеюсь, что помог тебе

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...