È possibile simulare e inserire tratti in PHPUnit?

7

Ho bisogno di estendere una classe di terze parti che non posso modificare. Le dipendenze della classe sono per lo più iniettate attraverso il costruttore, rendendole facili da prendere in giro. Tuttavia, la classe utilizza anche metodi tratti da un tratto che chiama direttamente i servizi globali, distruggendo l'isolamento del test. Ecco un esempio di codice che illustra il problema:

trait SomeTrait {
  public function irrelevantTraitMethod() {
    throw new \Exception('Some undesirable behavior.');
  }
}

class SomeSystemUnderTest {
  use SomeTrait;
  public function methodToTest($input) {
    $this->irrelevantTraitMethod();
    return $input;
  }
}

class MockWithTraitTest extends \PHPUnit_Framework_TestCase {
  public function testMethodToTest() {
    $sut = new SomeSystemUnderTest();
    $this->assertSame(123, $sut->methodToTest(123)); // Unexpected exception!
  }
}

Come si fa ad affrontare una situazione come questa in PHPUnit? I tratti possono essere derisi e iniettati in qualche modo? L'unica soluzione che ho trovato è di prendere in giro il SUT stesso e solo annullare i fastidiosi metodi del tratto, lasciando intatti i veri metodi SUT. Ma non sembra giusto.

    
posta TravisCarden 10.01.2016 - 19:22
fonte

2 risposte

1

Speriamo che qualcuno possa fornire una risposta migliore ad un certo punto, ma nel frattempo - nel caso in cui qualcun altro abbia a che fare con lo stesso problema - ecco la soluzione a cui ho fatto riferimento nella domanda. Dato lo stesso tratto e SUT, il test sottostante passerà. Crea un "parziale simulato" del SUT, sostituendo solo il metodo del tratto e lasciando intatto il resto all'esercizio e all'asserimento.

class MockWithTraitTest extends \PHPUnit_Framework_TestCase {
  public function testMethodToTest() {
    /** @var \SomeSystemUnderTest|\PHPUnit_Framework_MockObject_MockObject $sut */
    $sut = $this->getMockBuilder('\SomeSystemUnderTest')
      ->setMethods(array('irrelevantTraitMethod'))
      ->getMock();
    $this->assertSame(123, $sut->methodToTest(123)); // OK.
  }
}

Questo approccio funziona, ma non mi piace perché temo che richieda un accoppiamento troppo stretto con l'implementazione e potrebbe portare a test fragili. (Che cosa succede se aggiungo un altro metodo al tratto, ad esempio per la registrazione, e lo utilizzo nel SUT? Ciò causerà errori di test irrilevanti finché non aggiorno tutti i miei mock.) Preferisco di gran lunga essere in grado di prendere in giro l'intero tratto.

    
risposta data 21.01.2016 - 05:15
fonte
0

Non c'è modo di fare quello che vuoi. I tratti sono un modo per definire un'API di classi API, allo stesso modo delle interfacce e dell'ereditarietà. Non puoi "deriderlo" come se stessi prendendo in giro una dipendenza.

Cosa potrebbe funzionare (havent provato): usa il reflection per "disabilitare" in modo dinamico tutti i metodi che definisce il tratto.
Tuttavia, c'è un altro problema con questo: la classe di implementazione può sovrascrivere i metodi del tratto o utilizzare un metodo di un'altra caratteristica con lo stesso nome. Semplicemente "disabilitando" tutti i metodi che il tratto "target" definisce non farà interamente il trucco.
Se sei fortunato, la riflessione ti aiuterà a risolvere anche questo.

    
risposta data 17.09.2016 - 15:11
fonte

Leggi altre domande sui tag