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.

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.


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

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().


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.


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ść.


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.