Как утверждать аргументы объекта вызова функции mock object

Рассмотрим

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

Ответ 1

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

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

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

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

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

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

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


Маленькая демонстрация

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

Второй соответствует двум и не подходит для аргумента 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.

Ответ 2

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

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

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

Текущая PHPUnit version 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
Ошибка ожидания для имени метода равна < string: callMe > при вызове 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 );
    }
}

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

1) SuTTest:: testSuT
Ошибка ожидания для имени метода равна < string: callMe > при вызове 1 раз
Не удалось утверждать, что две строки равны.
--- Ожидаемые
+++ Актуально
@@@@
-'hi '
+ 'no'

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

Ответ 3

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

$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());

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