2017-02-17 93 views
2

我有一個服務,它應該創建一個電子郵件類對象並將它傳遞給第三個類(email-sender)。如何從存根函數參數中獲取屬性?

我想檢查由函數生成的電子郵件的正文。

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

所以我創建emailService和電子郵件stub類。但我無法驗證電子郵件的正文。我也無法檢查$的電子郵件 - > setBody()被調用,因爲測試的功能

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

我這裏面創建的電子郵件:

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

在實際應用中產生的體,所以我想測試它是否正確生成。我怎樣才能做到這一點?

回答

2

在PHPSpec你不能讓這種斷言到創建的對象(甚至在存根或您在規範文件中創建他們嘲笑):你唯一可以匹配是SUS(小號 ystem ü nder S pec)及其返回值(如果有的話)。

我會寫一個小指南,讓您的測試通過,以提高您的設計和可測試性


什麼是從我的觀點錯了

new使用內部Service

爲什麼錯了

Service有兩個責任:創建一個Email對象並完成其工作。這個突破SRP SOLID principles。此外,您失去了控制對象的創建,這成了,因爲你發現,很難測試

解決方法,使規範通

我會建議使用一個工廠(我將在下面顯示),對於這種任務,因爲增加的可測試性顯着,但是,在這種情況下,你可以通過重寫規範讓你的測試通過如下

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

只要setBody在SUS實現不改變,這個工程。 但是我不會推薦它,因爲這應該是PHPSpec觀點中的一種氣味。

使用工廠

創建工廠

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

     return $email; 
    } 
} 

及其規範

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

現在你已經確保工廠的createEmail做你希望做什麼。正如你可以注意到的那樣,責任在這裏被封裝,你不需要擔心其他地方(想想你可以選擇如何發送郵件的策略:直接將它們放入隊列中等等;如果你用原始方法解決它們,您需要測試每個具體的策略,即按照您的預期創建電子郵件,而現在您不需要)。

整合工廠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); 
    } 
} 

最後做出規範通(正道)

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

我沒有嘗試過自己這個例子,可能有一些拼寫錯誤,但我100肯定這種方法:我希望所有讀者都清楚。

+0

謝謝。我真的很感激! – xorik

+0

我上傳了工作代碼:https://gist.github.com/xorik/89da2c3c065d1b6b6d10d915191305f2 – xorik