How to get property from stub function argument?

爷,独闯天下 提交于 2019-12-02 00:37:07

In PHPSpec you can't make this kind of assertion onto created objects (and even on stubs or mocks as you create them in spec file): only thing you can match on are SUS (System Under Spec) and its returned value (if any).

I'm gonna write a little guide to make your test pass and to improve your design and testability


What's wrong from my point of view

new usage inside Service

Why is wrong

Service has two responsibility: create an Email object and do its job. This break SRP of SOLID principles. Moreover you lost control over object creation and this become, as you spotted, very difficult to test

Workaround to make spec pass

I would recommend to use a factory (as I will show below) for this kind of task because increase testability dramatically but, in this case, you can make your test pass by rewriting the spec as follows

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

As long as setBody does not change in SUS implementation, this works. However I would not recommend it as this should be a smell from PHPSpec point of view.

Use a factory

Create the factory

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

        return $email;
    }
}

and its spec

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

Now you're sure that createEmail of the factory does what you expect to do. As you can notice, responsibility is incapsulated here and you don't need to worry elsewhere (think about a strategy where you can choose how to send mails: directly, putting them in a queue and so on; if you tackle them with original approach, you need to test in every concrete strategy that email is created as you expected whereas now you don't).

Integrate the factory in 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);
    } 
}

Finally make spec pass (the right way)

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

I did not tried myself this examples and there could be some typos but I'm 100% sure of this approach: I hope is clear for all readers.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!