PHPUnit测试是否调用了类方法

时间:2015-04-12 20:03:15

标签: phpunit

我有一个模型类,它在其中一个方法中调用mailer类:

class someModel{
    public function sendEmail($data){
         $mailer = new Mailer();
         $mailer->setFrom($data['from']);
         $mailer->setTo($data['to']);
         $mailer->setSubject($data['subject']);
         return $mailer->send();
    }
}

如何测试sendEmail方法?也许我应该模拟邮件程序类并检查是否所有这些邮件程序方法都是在sendMail方法中调用的?

我们将不胜感激。

3 个答案:

答案 0 :(得分:2)

包装Mailer类的IMO无法解决您所面临的问题,即您无法控制正在使用的Mail实例。

问题来自于在需要它们的对象中创建依赖项,而不是像这样在外部注入它们:

class someModel{

  private $mailer;

  public function __construct(Mailer $mailer) {
       $this->mailer = $mailer;
  }

  public function sendEmail($data){         
     $this->mailer->setFrom($data['from']);
     $this->mailer->setTo($data['to']);
     $this->mailer->setSubject($data['subject']);
     return $this->mailer->send();
  }
}

创建someModel实例时,必须传递Mail实例(这是一个外部依赖项)。并且在测试中你可以传递一个Mail mock ,它会检查是否正在进行正确的调用。


替代:

如果您认为注入Mail实例是错误的(可能是因为有很多someModel实例),或者您只是无法以这种方式更改代码,那么您可以使用服务存储库,这将保留单个Mail实例,并允许您在外部设置它(再次,在测试中,您将设置模拟)。

尝试使用Pimple之类的简单内容。

答案 1 :(得分:1)

我会(并且在我自己的代码中使用Mailer!)将您的Mailer实例包装在您编写的类中。换句话说,制作自己的使用Mailer的Email类。这允许您将Mailer的界面简化为您需要的界面,并且更容易模拟它。它还使您能够在以后无缝地替换Mailer。

在包装类以隐藏外部依赖项时要记住的最重要的事情是保持包装类简单。它的唯一目的是让你换掉电子邮件库类,而不是提供任何复杂的逻辑。

示例:

class Emailer {
    private $mailer = new Mailer();

    public function send($to, $from, $subject, $data) {
         $this->mailer->setFrom($from);
         $this->mailer->setTo($to);
         ...
         return $mailer->send();
    }
}

class EmailerMock extends Emailer {
    public function send($to, $from, $subject, $data) {
         ... Store whatever test data you want to verify ...
    }

    //Accessors for testing the right data was sent in your unit test
    public function getTo() { ... }
    ...
}

对于想要触摸我软件外部的所有类/库,我遵循相同的模式。其他好的候选者是数据库连接,Web服务连接,缓存连接等。

修改 gontrollez在他关于依赖注入的回答中提出了一个很好的观点。我没有明确地提到它,但是在创建包装器后,您希望使用某种形式的依赖注入将其放入您想要使用它的代码中。传入实例可以使用Mocked实例设置测试用例。

执行此操作的一种方法是将实例传递给构造函数,如gontrollez建议的那样。在很多情况下,这是最好的方法。但是,对于我正在嘲笑的“外部服务”,我发现该方法变得乏味,因为很多类最终需要传入实例。例如,考虑一个数据库驱动程序,你想要为你的测试进行模拟,但你使用了很多不同的班级。所以我所做的就是用一个方法创建一个单例类,让我一次模拟整个事物。然后,任何客户端代码都可以使用单例来访问服务,而无需知道它是否被模拟。它看起来像这样:

class Externals {
    static private $instance = null;
    private $db = null;
    private $email = null;
    ...

    private function __construct() {
        $this->db = new RealDB();
        $this->mail = new RealMail();
    }

    static function initTest() {
        self::get();         //Ensure instance created
        $db = new MockDB();
        $email = new MockEmail();
    }

    static function get() {
        if(!self::$instance)
            self::$instance = new Externals();
        return self::$instance;
    }

    function getDB() { return $this->db; }
    function getMail() { return $this->mail; }
    ....
}

然后你可以使用phpunit的bootstrap文件功能来调用Externals :: initTest(),你的所有测试都会被模拟的外部设置!

答案 2 :(得分:0)

首先,正如RyanW所说,你应该为Mailer编写自己的包装器。

其次,要测试它,请使用模拟:

<?php

class someModelTest extends \PHPUnit_Framework_TestCase
{
    public function testSendEmail()
    {
        // Mock the class so we can verify that the methods are called
        $model = $this->getMock('someModel', array('setFrom', 'setTo', 'setSubject', 'send'));
        $controller->expects($this->once())
            ->method('setFrom');
        $controller->expects($this->once())
            ->method('setTo');
        $controller->expects($this->once())
            ->method('setSubject');
        $controller->expects($this->once())
            ->method('send');
        $model->sendEmail();
    }
}

上面的代码未经测试,但它基本上嘲弄someModel类,为sendEmail内调用的每个函数创建虚函数。然后进行测试以确保在sendEmail被调用时,sendEmail调用的每个函数都被调用一次。

See the PHPUnit docs了解有关模拟的更多信息。