INCLUDE_DATA

Automated Testing Using Zend Framework, Part 1

In: PHP

4 Jan 2009

Automated testing for your web applications is an important step in having the confidence to make changes to your application, and still be confident you’re delivering a quality, regression-free product. With Zend Framework’s testing framework (built with PHPUnit), you can build a thorough suite of test cases for your web application with very little hassle.

Part 1 will give you all of the basic information you need to start writing automated tests for your Zend Framework applications. In Part 2, I’ll provide more real-world example covering some other ways to write tests.

Lets get right to it.

For the examples below, I will be using an actual controller from one of my projects. This controller handles account activies, such as logins, logouts, registrations, and confirmations. We’ll be using a test database with a schema that clones our production database, and Doctrine to manage ORM (Sorry Zend_Db :( ). I’ll assume that you’re using the prescribed Zend Framework (1.6+) project layout, and that you’re somewhat familiar with Zend_Config and using an Initializer controller plugin (created by default if you’re using Zend Studio for Eclipse 6.1).

Preparing Your Application

The first step to setting up automated testing is to prepare your application’s environment and settings appropriately. Depending no your setup, this can involve setting global variables, changing database connections, or reconfiguring paths. Fortunately for us, this capability is easy using Zend_Config and an Initializer controller plugin.

Zend_Config allows you to specify “sections”, which can inherit from another section. This allows you to modify configuration for different environments without duplicating settings across different files (and thereby helping us ensure we don’t forget to set something!). In our example project, we’ll need only to modify our database connection string, so we’re using the test database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
< ?xml version="1.0" encoding="UTF-8"?>
<config>
 
<production>
  <db>
	<dsn>mysql://dbowner:password@localhost/maindb</dsn>
	<attributes>
		<model_loading>conservative</model_loading>
	</attributes>
  </db>
</production>
<test extends="production">
  <db>
	<dsn>mysql://dbowner:password@localhost/maindb_test</dsn>
  </db>
</test>
</config>

Notice how we can even inherit child attributes: Even though we specified a a node, we didn’t have to specify everything below the node

Now that we have our configurations in order, we need something to manage this configuration for us, and switch off based on the environment we’re running in. That’s the role of our Initializer plugin, which accepts the environment to initialize as a constructor parameter. Showing the source of an Initializer is outside the scope of this article, but here’s a pastie for the curious.

A Basic Example

Lets start with the basic framework of a controller test. If you’re using Zend Studio for Eclipse, you can easily create this structure by right-clicking on your controller in PHP Explorer and selecting New > Zend Framework Item, and then selecting Zend Controller Test Case. Then, simply make sure the controller you want to test is chosen, and click finish.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';
require_once 'application/Initializer.php';
require_once 'application/default/controllers/IndexController.php';
 
class AccountControllerTest extends Zend_Test_PHPUnit_ControllerTestCase {
 
	/**
	 * Prepares the environment before running a test.
	 */
	protected function setUp() {
		$this->bootstrap = array ($this, 'appBootstrap' );
		parent::setUp ();
		// TODO Auto-generated FooControllerTest::setUp()
	}
 
	/**
	 * Prepares the environment before running a test.
	 */
	public function appBootstrap() {
		$this->frontController->registerPlugin ( new Initializer( 'test' ) );
	}
 
	/**
	 * Cleans up the environment after running a test.
	 */
	protected function tearDown() {
		// TODO Auto-generated FooControllerTest::tearDown()
		parent::tearDown ();
	}
 
	/**
	 * Constructs the test case.
	 */
	public function __construct() {
		// TODO Auto-generated constructor
	}
 
	/**
	 * Tests FooController->barAction()
	 */
	public function testIndexAction() {
		// TODO Auto-generated FooControllerTest->testBarAction()
		$this->dispatch ( '/index/index' );
		$this->assertController ( 'index' );
		$this->assertAction ( 'index' );
	}
}

You’ll see on line 20 where we’re using our initializer to setup the test environment before we run the tests. Before each test metod is run, PHPUnit will call our setup() method, which has been programmed to call our appBootstrap method. This ensures us that we’re using a clean configuration and environment before each test, as if each test was a separate process. When each test case is done, the tearDown() method is called. tearDown() is the place to put any code for removing resources or resetting any persistable changes that tests might make. We’ll make use of this in our advanced examples later.

Line 41 contains a bare-bones test case, which will ensure that dispatching to ‘/index/index’ results in the controller named ‘index’ and an action called ‘index’ are the last to be executed. This might seem trivial, but it helps detect errors with your controllers. If an uncaught exception is thrown, the controller assertion will fail, since your controller will be the last executed.

Running Your Tests

To keep this article focused, I’ve decided to remove this section and cover only writing the tests. If you need help creating Test Suites and running tests from the command line, check out PHPUnit Documentation, specifically the sections on the Command Line Test Runner and Organizing Test Suites. If you have any specific questions, feel free to shoot me an email.

Extending the Testing Functionality

Now that we covered the basics, lets get to fully testing our Accounts controllers. There are a couple of “outside the box” requirements we have for testing our accounts controller. First off, we need a way to test the full account creation process, as if a user was actually registering. When we’re done with a test, we want to get rid of that data whenever possible, so we can run tests as many times as we want and not worry about growing our test database. Secondly, We need a way to simulate an authenticated user, as well as checking for whether a user has been authenticated or not.

Because these operations are pretty general and an opportunity for reuse exists, Lets put our supporting logic in a parent class and let our test cases inherit them.

Disposable Testing Models

There are two ways to ensure the data you’re creating during a test is deleted once a test is complete. Some choose to create the database on the fly using seed data. Since I’m using Doctrine for my projects and working directly with models (no raw queries) in the tests, I decided that just deleting the data was the best approach. To do this, all we need to do is “schedule” our model for deletion after it has been created (or loaded).

1
2
3
4
protected function _setDisposable( Doctrine_Record $model )
{
    $this->_disposables[] = $model;
}

This function simply takes a reference to a model, and stores it in array, which will be handled by our tearDown() function later:

1
2
3
4
5
6
7
8
9
10
11
12
13
	protected function tearDown()
 
 
	{
	    parent::tearDown();
 
	    foreach ( $this->_disposables as $model ) {
	        if ( $model instanceof Doctrine_Record ) {
	            $model->delete();
	        }
	        unset( $model );
	    }
	}

We simply loop through all of our “scheduled” models and delete them. This must be done in tearDown() and not inside of any test method because it’s the only way to ensure it happens. Once an assertion fails or an unexpected exception occurs in a test, that method stops executing. IF we were to try to dispose our models after an assertion, it may never happen1. Similarly, we obviously can’t dispose of a model before an assertion if that model is needed for the assertion (and why would it exist if you didn’t need it?).

Authentication Support

There are 3 things we must be able to do in order to fully test authentication.

  • Create a fake identity
  • Set our environment to a state equivalent to a user being logged in.
  • Assert whether the environment has been changed to a logged in state.

Our example controller uses a database adapter for authentication and identity retrieval2, so generating a fake identity for us means creating (or loading) a record in our accounts table, and returning the identity data we would normally get.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * Generates a fake identity, usefull for simulating a logged in user
 *
 * @return StdClass an identity
 */
protected function _generateFakeIdentity()
{
	$identity = new stdClass();
 
        $account = new Account();
        $account->username     = 'AutoTest' . time();
        $account->emailAddress = 'autotest@example.org';
        $account->password     = md5( 'password' );
        $account->confirmed    = true;
        $account->enabled      = true;
        $account->save();
        $this->_setDisposable( $account );
 
	foreach( $account->toArray() as $key => $val ) {
	        $identity->$key = $val;
	}
	unset( $identity->password );
 
	return $identity;
}

Account is our model, a Doctrine_Record type. We’re just simply creating a random account, and returning it’s data as our idenity. Notice that we’re also scheduling this model for disposal (as we covered above.) Now, we just need a way to set our environment to the “logged in state” as this fake user.

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Sets the current state as if there is a logged in user
 *
 * @param object $identity the idenity to use, otherwise one is generated
 * @return void
 */
protected  function _doLogin( $identity = null )
{
    if ( $identity === null ) {
        $identity = $this->_generateFakeIdentity();
    }
    Zend_Auth::getInstance()->getStorage()->write( $identity );
}

In our example application, if Zend_Auth has an identity, a user must be logged in. Therefore, all we have to do is store an identity in Zend_Auth’s storage adapter, and call ourselves logged in. That makes asserting login as simple as checking for an identity.

1
2
3
4
5
6
7
8
9
public function assertNotLoggedIn()
{
    $this->assertFalse( Zend_Auth::getInstance()->hasIdentity(), 'Login assertion failed' );
}
 
public function assertLoggedIn()
{
    $this->assertTrue( Zend_Auth::getInstance()->hasIdentity(), 'Login assertion failed' );
}

These simple assertions 3 ensure that we’re logged in (or not logged in)

Putting It All Together

Putting it all together, we now have a base class which provides all of our test cases with the functionality we need.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
< ?php
 
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';
 
class BaseControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
 
    /**
     * Contains models which should be destroyed on tear down
     *
     * @var array
     */
    protected $_disposables = array();
 
 
	protected function tearDown()
	{
	    parent::tearDown();
 
	    foreach ( $this->_disposables as $model ) {
	        if ( $model instanceof Doctrine_Record ) {
	            $model->delete();
	        }
	        unset( $model );
	    }
	}
 
	/**
	 * Sets a model as disposable, so teardown automatically deletes it
	 *
	 * @param Doctrine_record $model
	 */
	protected function _setDisposable( Doctrine_record $model )
	{
	    $this->_disposables[] = $model;
	}
 
	/**
	 * Sets the current state as if there is a logged in user
	 *
	 * @param object $identity the idenity to use, otherwise one is generated
	 * @return void
	 */
	protected function _doLogin( $identity = null )
	{
	    if ( $identity === null ) {
	        $identity = $this->_generateFakeIdentity();
	    }
 
	    Zend_Auth::getInstance()->getStorage()->write( $identity );
	}
 
	/**
	 * Generates a fake identity, usefull for simulating a logged in user
	 *
	 * @param boolean $unique
	 * @return StdClass an identity
	 */
	protected function _generateFakeIdentity( $unique = false )
	{
		$identity = new stdClass();
 
		$account = new Account();
	        $account->username     = 'AutoTest' . time();
	        $account->emailAddress = 'autotest' . time() . '@example.org';
	        $account->password     = md5( 'password' );
	        $account->confirmed    = true;
	        $account->enabled      = true;
	        $account->save();
	        $this->_setDisposable( $account );
 
	    	foreach( $account->toArray() as $key => $val ) {
	        	$identity->$key = $val;
	    	}
	    	unset( $identity->password );
 
	    	return $identity;
	}
 
	public function assertNotLoggedIn()
	{
	    $this->assertFalse( Zend_Auth::getInstance()->hasIdentity(), 'Login assertion failed' );
	}
 
	public function assertLoggedIn()
	{
	    $this->assertTrue( Zend_Auth::getInstance()->hasIdentity(), 'Login assertion failed' );
	}
 
}

Writing Our Controller Test

Now that our groundwork has been laid, we can finally start writing our controller test.

Our first set of test cases cover the requirement “when a user registers, they must confirm their email address before they can access their account”. To do this, we need to simulate a user posting their valid registration details to our controller, and verify that the account created is not set as confirmed. We then need to test that submitting login information for a non-confirmed account to our login action does not result in an authenticated user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public function testRegisterCreatesNewUnconfirmedAccount()
{
    $email = 'autotest' . time() . '@example.org';
    $data = array(
        'emailAddress'    => $email,
        'password'		  => 'testpassw0rd',
        'passwordconfirm' => 'testpassw0rd'
    );
 
    $_POST = $data;
 
    $this->dispatch( '/account/register' );
    //try to find the account record
    $table = Doctrine_Table::create( 'Account' ) ;
    $account = $table->findOneByEmailAddress( $email );
    $this->_setDisposable( $account );
    $this->assertNotNull( $account );
    $this->assertFalse( $account->confirmed, 'Account was not marked as unconfirmed' );
}
 
/**
 * Asserts that a user that hasn't been confirmed cannot login
 *
 */
public function testUnconfirmedUserCannotLogin()
{
    $email = 'autotest' . time() . '@example.org';
 
    $account = new Account();
    $account->username     = $email;
    $account->password     = md5( 'password' );
    $account->emailAddress = $email;
    $account->confirmed    = false;
    $account->enabled      = true;
    $account->save();
 
    $this->_setDisposable( $account );
 
    $_POST['username'] = $email;
    $_POST['password'] = 'password';
 
 
 
 
    $this->dispatch( '/account/login' );
    $this->assertFalse( Zend_Auth::getInstance()->hasIdentity() );
    $this->assertNotRedirect();
}

Our first test simply uses the $_POST global variable to simulate submitting our registration form with some test data. After dispatch(), we use Doctrine_Table to find the model created by AccountController::registerAction(), then assert that the record was found and that it was not marked as confirmed.

The second test works by manually inserting a record that’s not confirmed, and making sure no user is authorized when attempting to login with that user’s account information. As an added bonus, we also use assertNotRedirect() to make sure our controller didn’t redirect. Our controller should only redirect if login was successful, otherwise it would be confusing to a user.

Conclusion

Automated testing of your controllers is relatively simple using the combined power of PHPUnit and Zend Framework’s Zend_Test component. We can add additional functionality to allow our tests to simulate authentication, create fake identities, and even clean up our database after our tests have run. I showed you how you can put this all together to test a registration and confirmation process in your controllers.

In part 2, I’ll cover more areas of testing in our AccountsController, including testing our actions that require an authorized user in order to access them.

  1. Of course, if tearDown() is never run for some reason the models won’t be deleted either, but we have more control over this. A third way that isn’t discussed is creating an on_shutdown procedure, but that seems a little overkill
  2. I’m actually using ZendX_Doctrine_Auth_Adapter from the extras incubator
  3. Taknig this further, we should actually create new PHPUnit criteria instead of wrapping the assertTrue() criteria so that our failure message is different. That’s something for later, though
Share and Enjoy:
  • Twitter
  • DZone
  • Digg
  • Reddit
  • Technorati
  • email
  • Twitthis

13 Responses to Automated Testing Using Zend Framework, Part 1

Avatar

Darin Boyd

January 7th, 2009 at 7:15 pm

Great article. It’s good to see some of the heavier topics such as unit testing being written about in the PHP world. Keep up the good work.

Avatar

Sergej 'ZaZa' Kurakin

January 8th, 2009 at 6:41 am

Nice example!

Can you provide any examples, how to fill database with data and make test any action that inserts/modifies/deletes data from database tables? I know, that there is a PHPUnit extension for database tests, but how to run them together with Zend_Test_PHPUnit_ControllerTestCase?

Avatar

Chucho

January 8th, 2009 at 7:55 am

I’m not sure you fully understand the difference between functional and unit testing, since you are mixing both in the same test case. I’m guessing you are new to unit testing and I assume you never used other frameworks before. Rails, for example, draws a lot of circles around your tests, and it does a good job of helping you know what kinds of tests belong in each: Unit, Functional and Integration. Unit tests are written from a programmer’s perspective. They ensure that a particular method of a class successfully performs a set of specific tasks. Functional tests are written from a user’s perspective. These tests confirm that the system does what users are expecting it to.

I think this post is inaccurate and misleading, specially for those PHP developers who are not familiar with testing.

Avatar

A.J. Brown

January 8th, 2009 at 8:14 am

@Chucho:

Never did I say that this was unit testing, functional testing, or integration testing. The title of the post is “Automated testing”, which encompass all of them. You won’t find the word “unit test” anywhere, nor did I make any comparison to Rails. This is simply a demonstration of how to solve a particular testing need from using the tools provided.

In fact, none of the test cases presented are unit tests, nor is there anything in that code that needs a unit test. The models are covered by Doctrine’s Tests, and Zend_Controller_Action is obviously covered by the Zend test suite. They’re all functional tests proving user stories / requirements. Taking it outside this post, the sample code I used is part of an “acceptance” test suite that Phing must run (and pass) before it will create the tarball, which is used to deploy the application.

I’d like to hear your feedback on why this is not a good approach.

Avatar

A.J. Brown

January 8th, 2009 at 8:30 am

Sergej ‘ZaZa’ Kurakin :
Nice example!
Can you provide any examples, how to fill database with data and make test any action that inserts/modifies/deletes data from database tables? I know, that there is a PHPUnit extension for database tests, but how to run them together with Zend_Test_PHPUnit_ControllerTestCase?

We use PHPUnit’s database test extension here at work. Either I’m not familiar enough with it to use it efficiently, or it really is a pain to use.

IF I’m understanding you correctly, you can use seed data to make sure the data, then check the data to make sure it has been modified. (In a gist, that’s how we’ve used PHPUnit’s database tests).

For Part 3, I’m working on demonstrating using the flexibility of Doctrine with in-memory databases to do testing against seeded data sets. (I know, that comes after Part 2 which is almost done :) )

Avatar

A.J. Brown

January 8th, 2009 at 8:34 am

A.J. Brown :
@Chucho:
You won’t find the word “unit test” anywhere, nor did I make any comparison to Rails.

Sorry, I see where you get the Unit test part from. That’s the title from DevZone, not here :)

Avatar

Chucho

January 8th, 2009 at 8:37 am

I know, but it might be confusing for beginners. I recommend the following dir structure:

/fixtures
/functional
/integration
/unit

Most PHP developers I know think functional and unit testing are the same thing. Maybe you can explain them the difference in “Automated Testing Using Zend Framework” Part 2.

http://en.wikipedia.org/wiki/Functional_test
http://en.wikipedia.org/wiki/Unit_testing

Avatar

A.J. Brown

January 8th, 2009 at 8:47 am

Good points… I’ll add that to the second part.

P.S., it’s weird that it put your comment in Moderation, but it hasn’t put previous ones…

Avatar

Jason

January 9th, 2009 at 2:15 pm

Any chance we could see the contents of Initializer.php? So far I haven’t come across a complete Zend_Test_PHPUnit_ControllerTestCase example.

Avatar

Jason

January 9th, 2009 at 2:29 pm

Nevermind, I didn’t read carefully enough the first time.

Avatar

Andrew

January 9th, 2009 at 6:14 pm

Thanks for your article. I’ve been trying to find more of them regarding Zend_Test. I’m trying to get started with testing my applications but I’m not that knowledgeable on the subject. I’m still a little confused about the whole process of using Zend_Test. Dumb question: do I need to download PHPUnit? Or is that included with Zend_Test? And the directory structure…where should I put my tests, PHPUnit, etc?

Also…I’m not using Zend Studio. I’m using Aptana. Can you lead me in the right direction?

Avatar

Oleg Lobach

January 11th, 2009 at 1:36 am

Very nice article!

I want to translate it to Russian if you don’t mind.

Avatar

Write Automated Unit Tests for Zend Framework Applications | The Desi Video Blog

January 21st, 2009 at 8:15 pm

[...] of a two-part series explaining how to write automated unit tests for Zend Framework applications. His article, which makes use of PHPUnit and Zend_Test in the context of a user account controller, also [...]

About this blog

A.J. Brown is a proven software engineer with over 7 years of experience specializing in smart web applications. He has a passion for Software as a Service (SaaS) and leveraging the Web as a platform. He strongly supports Open Source software, and is a minor contributor to Zend Framework, Doctrine ORM, and other open source projects.