phpunit evita argumentos de construtor para simulação

85

Qual é a maneira de evitar que o phpunit tenha que chamar o construtor de um objeto mock? Caso contrário, eu precisaria de um objeto fictício como argumento do construtor, outro para isso etc. A api parece ser assim:

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

Eu não consigo fazer funcionar. Ele ainda reclama do argumento do construtor, mesmo com $callOriginalConstructordefinido como falso.

Tenho apenas um objeto no construtor e é uma injeção de dependência. Então eu não acho que tenho um problema de design aí.

yhw42
fonte

Respostas:

139

Você pode usar em getMockBuildervez de apenas getMock:

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

Veja a seção "Test Doubles" na documentação do PHPUnit para detalhes.

Embora você possa fazer isso, é muito melhor não precisar. Você pode refatorar seu código para que, em vez de uma classe concreta (com um construtor) ser injetada, você dependa apenas de uma interface. Isso significa que você pode simular ou criar um stub da interface sem precisar dizer ao PHPUnit para modificar o comportamento do construtor.

dave1010
fonte
Isso funciona muito bem para mim. Deve ser o exemplo 10.3, no entanto. Eu tentei editar o post, mas SO chutei de volta por ser uma edição muito curta.
Mateus
Obrigado @Lytithwyn. Atualizou a resposta.
dave1010
42

Aqui está:

    // 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
fonte
Isso parece ser quase o que eu quero. Eu quero chamar o getMock apenas com classe a ser ridicularizada e o $ callMockConstructor. Quão? algo como isso: $ this-> getMock ($ classToMock, $ callMockConstructor). A única coisa que consegui pensar é ir no código-fonte do PHPUnit e alterá-lo para default = false.
Gutzofter
1
Mudei o padrão para falso em testcase.php. Você pensaria que seria definido como falso por padrão. Zombar de um construtor parece muito estranho
Gutzofter
Excelente resposta. Exatamente o que eu estava procurando
Hades
4

Como um adendo, eu queria anexar expects()chamadas ao meu objeto simulado e, em seguida, chamar o construtor. No PHPUnit 3.7.14, o objeto que é retornado quando você chama disableOriginalConstructor()é literalmente um objeto.

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

Infelizmente, no PHP 5.4 há uma nova opção que eles não estão usando:

ReflectionClass :: newInstanceWithoutConstructor

Como isso não estava disponível, tive que refletir manualmente a classe e, em seguida, invocar o construtor.

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

Observe, se functionCallFromConstructfor protected, você precisa usar especificamente para setMethods()que o método protegido seja simulado. Exemplo:

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

setMethods()deve ser chamado antes da expect()chamada. Pessoalmente, eu encerro isso depois, disableOriginalConstructor()mas antes getMock().

Steve Tauber
fonte
Não tenho ideia se isso é um cheiro de código, mas funcionou muito bem para mim e eu só queria agradecer a você.
devbanana de
1

Talvez você precise criar um stub para passar como o argumento do construtor. Então você pode quebrar essa cadeia de objetos fictícios.

Glenn Moss
fonte
1

Como alternativa, você pode adicionar um parâmetro para getMock para evitar a chamada do construtor padrão.

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

Ainda assim, acho que a resposta de dave1010 parece melhor, isso é apenas por uma questão de completude.

Hans Wouters
fonte
1

Esta questão é um pouco antiga, mas para novos visitantes, você pode fazer isso usando o createMockmétodo (anteriormente chamado createTestDoublee introduzido na v5.4.0).

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

Como você pode ver no código abaixo extraído da PHPUnit\Framework\TestCaseclasse (in phpunit/src/framework/TestCase.php), ele basicamente criará um objeto simulado sem chamar o construtor original .

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

PHPUnit é projetado para chamar o construtor em objetos simulados; para evitar isso, você deve:

  1. Injete um objeto simulado como uma dependência no objeto do qual você está tendo problemas para simular
  2. Crie uma classe de teste que estenda a classe que você está tentando chamar e que não chame o construtor pai
silfreed
fonte