Posts Tagged ‘events’

Events functional testing

Like many components of the framework, Events are highly testable.

A real world example : an email logger, which I already covered here. We want to test the integration of this functionality throughout the application.

A good practice in OOP is to decouple the logic from the implementation. Both must be isolated : Unit testing (white box) for the implementation, and functional testing (black box) for the logic.

You may wonder, what belongs to unit or functional testing ?

To understand the difference, let’s ask the good questions :

In my code, does the email class generates emails from templates correctly, using my SMTP server with SSL encryption ?

This is pure implementation matters. I have to mock some classes, generate mock email, test config files, and even send it to myself to see if the it works. If I use native sendmail, we must check whether the program is running on the hosting server, that the server is actually reachable etc.

To answer these questions, write Unit Tests.

In my application, does an email is sent to the author when he updates an article ?

Now we talk business logic. Whatever library or server used behind, we just want to make sure that the application fulfills its functional requirements. During this test, we assume that the underlying email mechanism simply works in all cases, with every sending strategy unit-tested in isolation.

To answer this one, which involves more than one step to complete, write a Functional Test.

Implementation example

Suppose that our application registers a ‘mail.log’ listener that will handle the email processing and sending when notified.

The listener for ‘mail.log’ events is registered in the application configuration class. This is where all the email sending implementation is done :

class backendConfiguration extends sfApplicationConfiguration
{
  public function configure()
  {
     $this->dispatcher->connect('mail.log', array($this, 'listenToMailLog'));
  }

  public function listenToMailLog(sfEvent $event)
  {
    if ($this->getEnvironment() == 'prod')
    {
      $connection = new Swift_Connection_SMTP('smtp.domain.com');
      $mailer = new Swift($connection);
      $recipients = new Swift_RecipientList();
      $recipients->addTo($event['to']);
      $message    = new Swift_Message($event['subject'], $event['body'], 'text/html');
      $mailer->send($message, $recipients, $smtp_config['sender']);
    }
  }
}

Note : To go even further, you can imagine some emailLogger classes that implement several strategies of email generation and sending.

Here is the code to use when we want to send an email  :

class articleActions extends sfActions
{
  public function executeUpdate($request)
  {

    // Update code here...

    $this->getContext()->getEventDispatcher()->notify(new sfEvent($this, 'mail.log', array(
      'subject' => '[ARTICLE UPDATE]',
      'body' => 'You have updated the article>',
      'to' => 'me@domain.com'
    )));
  }
}

Testing the event

Let’s create a simple event listener for test purposes. During the test sequence, we want to catch all the notified ‘mail.log’ events, and check if they match some expectations :

include(dirname(__FILE__).'/../../bootstrap/functional.php');

class mockEventListener
{
  public function __construct()
  {
    $this->event = new sfEvent(null, null);
  }

  public function listenToMailLog(sfEvent $event)
  {
    $this->event = $event;
  }

  public function getLastEvent()
  {
    $last_event = $this->event;
    $this->event = new sfEvent(null, null);
    return $last_event;
  }
}

$mockEventListener = new mockEventListener;

$dispatcher = sfContext::getInstance()->getEventDispatcher();
$dispatcher->connect('mail.log', array($mockEventListener, 'listenToMailLog'));

$browser = new sfTestBrowser();

$browser->post('/article/update', array('title' => 'newtitle'))->
  isRequestParameter('module', 'article')->
  isRequestParameter('action', 'update');

$last_event = $mockEventListener->getLastEvent();

$browser->test()->is(
  $last_event->getName(),
  'mail.log',
  'An "email.log" event should have been notified'
);

$browser->test()->is(
  $last_event['subject'],
  '[ARTICLE UPDATE]',
  'The "subject" should be "[ARTICLE UPDATE]"'
);

Let’s run the test now :

$ symfony test:functional backend articleActions

functional

As the number of tests increase, the test suite grows and it becomes a real live documentation about every functional aspects of the application.