phpunit unika argumentów konstruktora dla makiety

85

W jaki sposób można uniknąć konieczności wywoływania konstruktora przez phpunit w celu uzyskania pozorowanego obiektu? W przeciwnym razie potrzebowałbym obiektu pozorowanego jako argumentu konstruktora, innego do tego itp. Interfejs API wygląda następująco:

getMock($className, $methods = array(), array $arguments = array(),
        $mockClassName = '', $callOriginalConstructor = TRUE,
        $callOriginalClone = TRUE, $callAutoload = TRUE)

Nie sprawiam, że to działa. Nadal narzeka na argument konstruktora, nawet przy $callOriginalConstructorustawieniu na false.

Mam tylko jeden obiekt w konstruktorze i jest to iniekcja zależności. Więc nie sądzę, żebym miał tam problem projektowy.

yhw42
źródło

Odpowiedzi:

139

Możesz użyć getMockBuilderzamiast tylko getMock:

$mock = $this->getMockBuilder('class_name')
    ->disableOriginalConstructor()
    ->getMock();

Zobacz sekcję „Test podwójny” w dokumentacji PHPUnit po szczegóły.

Chociaż możesz to zrobić, o wiele lepiej nie musisz tego robić. Możesz refaktoryzować swój kod, więc zamiast konkretnej klasy (z konstruktorem) wymagającej wstrzyknięcia, polegasz tylko na interfejsie. Oznacza to, że możesz mockować lub odgrywać interfejs bez konieczności informowania PHPUnit o modyfikowaniu zachowania konstruktora.

dave1010
źródło
To działa świetnie dla mnie. Powinien to być przykład 10.3. Próbowałem edytować post, ale SO kopnąłem go z powrotem, ponieważ był zbyt krótki.
Matthew
Dzięki @Lytithwyn. Zaktualizowałem odpowiedź.
dave1010
42

Proszę bardzo:

    // Get a Mock Soap Client object to work with.
    $classToMock = 'SoapClient';
    $methodsToMock = array('__getFunctions');
    $mockConstructorParams = array('fake wsdl url', array());
    $mockClassName = 'MyMockSoapClient';
    $callMockConstructor = false;
    $mockSoapClient = $this->getMock($classToMock,
                                     $methodsToMock,
                                     $mockConstructorParams,
                                     $mockClassName,
                                     $callMockConstructor);
Matthew Purdon
źródło
Wydaje się, że jest to prawie to, czego chcę. Chcę wywołać metodę getMock tylko z klasą, która ma być mockowana, i $ callMockConstructor. W jaki sposób? coś takiego: $ this-> getMock ($ classToMock, $ callMockConstructor). Jedyne, o czym mógłbym pomyśleć, to przejść do źródła PHPUnit i zmienić je na default = false.
Gutzofter
1
Zmieniłem domyślne na false w testcase.php. Można by pomyśleć, że będzie to domyślnie ustawione na fałsz. Kpiny z konstruktora wydają się bardzo dziwne
Gutzofter
Doskonała odpowiedź. Właśnie tego szukałem
Hades
4

Jako dodatek chciałem dołączyć expects()wywołania do mockowanego obiektu, a następnie wywołać konstruktora. W PHPUnit 3.7.14 obiekt, który jest zwracany podczas wywołania, disableOriginalConstructor()jest dosłownie obiektem.

// Use a trick to create a new object of a class
// without invoking its constructor.
$object = unserialize(
sprintf('O:%d:"%s":0:{}', strlen($className), $className)

Niestety, w PHP 5.4 jest nowa opcja, której nie używają:

ReflectionClass :: newInstanceWithoutConstructor

Ponieważ nie było to dostępne, musiałem ręcznie odzwierciedlić klasę, a następnie wywołać konstruktora.

$mock = $this->getMockBuilder('class_name')
    ->disableOriginalConstructor()
    ->getMock();

$mock->expect($this->once())
    ->method('functionCallFromConstructor')
    ->with($this->equalTo('someValue'));

$reflectedClass = new ReflectionClass('class_name');
$constructor = $reflectedClass->getConstructor();
$constructor->invoke($mock);

Zauważ, że jeśli functionCallFromConstructtak protected, musisz go specjalnie użyć, setMethods()aby metoda chroniona była kpiona. Przykład:

    $mock->setMethods(array('functionCallFromConstructor'));

setMethods()należy zadzwonić przed expect()wezwaniem. Osobiście łączę to po, disableOriginalConstructor()ale wcześniej getMock().

Steve Tauber
źródło
Nie mam pojęcia, czy to zapach kodu, ale to zadziałało świetnie i chciałem ci tylko podziękować.
devbanana
1

Być może musisz utworzyć kod pośredniczący, który zostanie przekazany jako argument konstruktora. Następnie możesz przerwać ten łańcuch pozorowanych obiektów.

Glenn Moss
źródło
1

Alternatywnie możesz dodać parametr do getMock, aby zapobiec wywołaniu domyślnego konstruktora.

$mock = $this->getMock(class_name, methods = array(), args = array(), 
        mockClassName = '', callOriginalConstructor = FALSE);

Mimo to myślę, że odpowiedź dave1010 wygląda ładniej, to tylko ze względu na kompletność.

Hans Wouters
źródło
1

To pytanie jest trochę stare, ale dla nowych użytkowników możesz to zrobić za pomocą createMockmetody (poprzednio wywołanej createTestDoublei wprowadzonej w wersji 5.4.0).

$mock = $this->createMock($className);

Jak widać w poniższym kodzie wyodrębnionym z PHPUnit\Framework\TestCaseklasy (in phpunit/src/framework/TestCase.php), w zasadzie utworzy on obiekt pozorowany bez wywoływania oryginalnego konstruktora .

/** PHPUnit\Framework\TestCase::createMock method */
protected function createMock(string $originalClassName): MockObject
{
    return $this->getMockBuilder($originalClassName)
                ->disableOriginalConstructor()
                ->disableOriginalClone()
                ->disableArgumentCloning()
                ->disallowMockingUnknownTypes()
                ->getMock();
}
Wesley Gonçalves
źródło
0

PHPUnit jest zaprojektowany do wywoływania konstruktora na mockowanych obiektach; aby temu zapobiec, należy:

  1. Wstrzyknij pozorowany obiekt jako zależność do obiektu, z którym masz problemy
  2. Utwórz klasę testową, która rozszerza klasę, którą próbujesz wywołać, która nie wywołuje konstruktora nadrzędnego
posrebrzany
źródło