SimpleMail

Test your web+email application using SimpleTest and PHP

What is this?

Here are a few add-on files for SimpleTest that enable you to run tests that include checking for and reading the contents of emails generated by the system under test.

Download

The current version is a first release, 16 Apr 2005:
simplemail-0.1.zip (4.25k)

Motivation

I recently had the need to run some automated tests on an e-commerce website which uses double opt-in for customer registrations in a similar manner the the system of Amazon. Testing such a system manually quickly becomes very annoying because of the need to constantly create new email addresses, then go through the registration and sign-up process, checking email, etc. It becomes difficult to test all the different scenarios because of the time reuqired to perform each manual test.

I had heard about SimpleTest and thought that it might have been possible to use it for this. It has an excellent simulated web browser ('SimpleBrowser') which acts just like a web browser but is intereacted with via PHP scripts. Unfortunately it didn't have any corresponding way to simulate a mail user agent (MUA) such as Mozilla Mail, Outlook, etc. I contacted the SimpleTest mailing list and I was told about the Fakemail project, but I thought that a little more work was required to make this integrate nicely with the SimpleTest testing platform.

The result is a basically functional SimpleMail class which can be passed a few very simple commands that allow emails to be loaded, MIME-decoded and read.

Example

Here is an example (download) which shows testing of a double opt-in process:
<?php

require_once("fakemail/fakemail.php");
require_once(
"fakemail/simplemail.php");

class
TestDoubleOptIn extends WebTestCase {

    var
$_fakemail;
    var
$emailaddress;
    var
$password;
    
    function
setUp(){
        
        
$this->emailaddress = TEST_EMAIL;
        
$this->password = TEST_PASSWORD;
    
        
$this->_fakemail=new FakeMailDaemon(NULL,NULL,10025);
        
$this->_fakemail->start();
    }
    
    function
tearDown(){
        
$this->_fakemail->stop();
    }
    
    function
testDoubleOptIn(){
        
        
$this->get(TEST_SERVER_HTTP);
        
$this->clickLink("register");
        
        
// form should exist with email address box:

        
$this->assertWantedPattern('#Please provide your email address#i');
        
$this->setField("email",$this->emailaddress);
        
        if(!
MAIL_TEST_MODE){
            
$this->fail("Server must be in test mode to run this test.");
            return
false;
        }
        
        
$this->clickSubmit('register');
        
        
$this->assertWantedPattern("#An email was sent to "
                
. preg_quote(trim($this->emailaddress))
                .
"#i"
        
);
        
        
// Go and check email
        
        
$simplemail = new SimpleMail($this->_fakemail);
        
$simplemail->login($this->emailaddress);
        
$simplemail->loadMessages();

        
$msg =& $simplemail->getLatestBySubject('#^New customer#i');

        if(
$msg===false){
            
$this->fail("Couldn't find 'new customer' email for ".$this->emailaddress);
            return
false;
        }                            

        
$url = $msg->findLinkAfterText("Please click the link below to confirm");

        if(
$url===false){
            
$this->fail("Couldn't find link in email: ".$msg->error);
            return
false;
        }

        if(!
preg_match("#^".preg_quote(TEST_SERVER_HTTPS)."/reg2.php\\?key=#",$url)){
            
$this->fail("Email link doesn't match expected: $url");
            return
false;
        }
        
        
// Click the email link...
        
        
$this->get($url);
        
$this->assertTitle("Enter your contact details");
        
        
// Fill in customer details form
        
        
$this->setField("title","Mr");
        
$this->setField("firstname","Sam");
        
$this->setField("lastname","Tiger");
        
$this->setField("address","77 Domestic Bliss St");
        
$this->setField("city","Sydney");
        if(!
$this->setField("country","Australia")){
            
$this->fail("Unable to set 'country' via selectbox");
            return
false;
        }
        
$this->setField("telno","999999");
        
$this->setField("password1",$this->password);
        
$this->setField("password2",$this->password);
        
        
$this->clickSubmit("continue");
        
        
$this->assertWantedPattern("#<h1>Thanks for Registering</h1>#i");
    }
}

?>


Quick Reference

The class SimpleMail has the following methods:

SimpleMail(&$fakemail)
Constructor. You need to pass a FakeMail object to this constructor. The FakeMail object tells SimpleMail where to read the emails from.
login($email)
FakeMail captures all SMTP traffic sent to a given host and port. By specifying the email address to SimpleMail, we are saying that we are only interested in reading emails sent to the stated recipient. Note that FakeMail makes no allowance for BCC or CC etc.
loadMessages()
This method reads the raw text of all emails sent to the email address since the FakeMail 'capture' directory was last emptied. So you want to make sure you empty that directory from time to time.
getMailCount()
Number of emails found for the email address specified
getMessageByNumber($num)
Load then n-th email address for the specified email address
getLatestBySubject($subjectpattern)
This method starts at the most recent email, MIME-decodes that email then checks the headers for that email. If the $subjectpattern is found in the email's Subject: header, the email is returned as a SimpleMailMessage object. Else the previous email is tried. If no matching emails are found, the return value is false.
getLatestBySender($senderpattern)
This works the same way, except it checks the mail's From: header.
The class SimpleMailMessage is a container for a MIME-decoded version of a message captured by FakeMail. It has the following methods:
SimpleMailMessage($rawcontent)
Constructor. Stored the raw content but doesn't decode it.
decode()
Makes a call to the PEAR::Mail_mimeDecode class to perform the MIME-decoding of the raw message content. If decoding runs successfully, the content is stored in $this, and $this->rawcontent is cleared out to save memory.
getPlainTextVersion()
This method checks that the main message body is of type text/plain and if so, returns the message body. Otherwise returns false. It is intended that this method be expanded to allow pulling out of the text version of an email from a multipart/alternative or multipart/mixed email, but this is not yet implemented. It might also be worth adding the ability to pull out a strip_tags() version of the HTML content.

What's Next?

  • SimpleMailMessage should be able to handle multipart and HTML messages
  • SimpleMail shouldn't do a full MIME decode of raw message content when scanning for subject/sender patterns
  • SimpleMail shouldn't need to load all those messages into memory (this would require modification of the FakeMailDaemon class though).
  • Examples and a test suite...

Contact Details

If you would like to discuss use of SimpleMail, I suggest you get onto the SimpleTest mailing list at
http://sourceforge.net/mail/?group_id=76550

Alternatively, you can contact me directly by email:
John Pye <jdpipe at users.s0urcef0rge.net>
(fix up the zeroes in the domain name)

License

This software is licensed under the same terms as SimpleTest (check the SimpleTest website for details), or, at your option, under the terms of the GNU General Public License, version 2.


Links