¿Cómo obtener la propiedad del argumento de la función stub?

Tengo un servicio, que debe crear un objeto de clase de correo electrónico y pasarlo a la tercera clase (remitente de correo electrónico).

Quiero verificar el cuerpo del correo electrónico, que se genera por la función.

Service.php

class Service { /** @var EmailService */ protected $emailService; public function __construct(EmailService $emailService) { $this->emailService = $emailService; } public function testFunc() { $email = new Email(); $email->setBody('abc'); // I want to test this attribute $this->emailService->send($email); } } 

Email.php:

 class Email { protected $body; public function setBody($body) { $this->body = $body; } public function getBody() { return $this->body; } } 

EmailService.php

 interface EmailService { public function send(Email $email); } 

Así que creo una clase de código auxiliar para emailService y correo electrónico. Pero no puedo validar el cuerpo del correo electrónico. Tampoco puedo verificar si se llamó $ email-> setBody () porque el correo electrónico se creó dentro de la función probada

 class ServiceSpec extends ObjectBehavior { function it_creates_email_with_body_abc(EmailService $emailService, Email $email) { $this->beConstructedWith($emailService); $emailService->send($email); $email->getBody()->shouldBe('abc'); $this->testFunc(); } } 

Tengo esto:

 Call to undefined method Prophecy\Prophecy\MethodProphecy::shouldBe() in /private/tmp/phpspec/spec/App/ServiceSpec.php on line 18 

En la aplicación real, el cuerpo se genera, por lo que quiero probar, si se genera correctamente. ¿Cómo puedo hacer eso?

En PHPSpec no puede hacer este tipo de afirmación en objetos creados (e incluso en trozos o burlas mientras los crea en el archivo de especificación): lo único con lo que puede coincidir es SUS ( S ystem U nder S pec) y su valor devuelto (Si alguna).

Voy a escribir una pequeña guía para hacer que tu pase de prueba y mejorar tu diseño y capacidad de prueba


Lo que está mal desde mi punto de vista

new uso dentro de Service

Por qué está mal

Service tiene dos responsabilidades: crear un objeto de Email y hacer su trabajo. Esta ruptura SRP de principios SÓLIDOS . Además, perdiste el control sobre la creación de objetos y esto se volvió, como viste, muy difícil de probar

Solución para hacer que la especificación pase

Recomendaría utilizar una fábrica (como mostraré a continuación) para este tipo de tarea porque aumenta la capacidad de prueba de forma espectacular pero, en este caso, puede hacer que pase su prueba reescribiendo las especificaciones de la siguiente manera

 class ServiceSpec extends ObjectBehavior { function it_creates_email_with_body_abc(EmailService $emailService) { $this->beConstructedWith($emailService); //arrange data $email = new Email(); $email->setBody('abc'); //assert $emailService->send($email)->shouldBeCalled(); //act $this->testFunc(); } } 

Siempre que setBody no cambie en la implementación de SUS, esto funciona. Sin embargo, no lo recomendaría ya que debería ser un olor desde el punto de vista PHPSpec.

Usa una fábrica

Crea la fábrica

 class EmailFactory() { public function createEmail($body) { $email = new Email(); $email->setBody($body); return $email; } } 

y su especificación

 public function EmailFactorySpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType(EmailFactory::class); } function it_creates_email_with_body_content() { $body = 'abc'; $email = $this->createEmail($body); $email->shouldBeAnInstanceOf(Email::class); $email->getBody()->shouldBeEqualTo($body); } } 

Ahora está seguro de que createEmail of the factory hace lo que espera hacer. Como puede observar, la responsabilidad está incluida aquí y no necesita preocuparse en ningún otro lugar (piense en una estrategia donde pueda elegir cómo enviar correos: directamente, colocándolos en una cola, etc., si los aborda con un enfoque original). , debe probar en cada estrategia concreta que el correo electrónico se crea como esperaba mientras que ahora no lo hace).

Integrar la fábrica en SUS

 class Service { /** @var EmailService */ protected $emailService; /** @var EmailFactory */ protected $emailFactory; public function __construct( EmailService $emailService, EmailFactory $emailFactory ) { $this->emailService = $emailService; $this->emailFactory = $emailFactory; } public function testFunc() { $email = $this->emailFactory->createEmail('abc'); $this->emailService->send($email); } } 

Finalmente haga que la especificación pase (de la manera correcta)

 function it_creates_email_with_body_abc( EmailService $emailService, EmailFactory $emailFactory, Email $mail ) { $this->beConstructedWith($emailService); // if you need to be sure that body will be 'abc', // otherwise you can use Argument::type('string') wildcard $emailFactory->createEmail('abc')->willReturn($email); $emailService->send($email)->shouldBeCalled(); $this->testFunc(); } 

No probé este ejemplo y podría haber algunos errores tipográficos, pero estoy 100% seguro de este enfoque: espero que esté claro para todos los lectores.