CakePHP automated web testing with SimpleTest

CakePHP/SimpleTest automated web testing: Can you share an example of some automated CakePHP SimpleTest web tests?

Sure, here's a quick look at how I've created a suite of CakePHP automated web tests using SimpleTest to perform integration testing on a new CakePHP web application.

The recipe for creating automated web tests in CakePHP is fairly simple:

  • Install SimpleTest
  • Create a class that extends CakeWebTestCase
  • Write your web tests
  • Run your web tests from a browser
  • Run your CakePHP web tests from the command line

The process of installing SimpleTest with CakePHP is covered on the CakePHP website, so I won't bother repeating that here. Instead what I'd like to do is share a large portion of my CakePHP web test class, and also show the URL to run my web/integration tests from the browser, and also run these same web tests from the command line.

My CakePHP web testing class

I won't discuss it too much unless there are any questions, but here's a small subset of my CakePHP web testing class. While I say "small subset", it's actually complete enough that you can see how to do a lot of things in a CakePHP SimpleTest web testing class, without too much repetition.

<?php

/*
 * run from command line like this:
 * prompt> cake testsuite app case views/complete_web
 */
class CompleteWebTestCase extends CakeWebTestCase {

  # text on the login page we can test for
  var $loginText = 'Please log in';

  function CompleteWebTestCase() {
    $port = '8888';
    $host = 'appmojo';
    $this->baseurl = 'http://' . $host . ':' . $port;
  }
  
  /*---------------- DATABASE CLEANUP STUFF -----------------*/
  
  function delete_bubba_from_db() {
    $link = mysql_connect('localhost', 'root', 'root');
    if (!$link) {
      die('Could not connect: ' . mysql_error());
    }
    mysql_select_db ('appmojo');

    $query = "delete from users where username='bubba'";
    $mysql_result = @ mysql_query ($query)
      or die ("Query '$query' failed with error message: \"" . mysql_error () . '"');
    mysql_close($link);
  }
  
  /* LOGIN FORM TESTS */
  
  function getBlankLoginForm() {
    $this->get($this->baseurl . "/users/login");
    $this->assertField('data[User][username]', '');
    $this->assertField('data[User][password]', '');
  }

  # make sure you can't get in without login credentials
  function testLoginNoCredentialsFails() {
    $this->getBlankLoginForm();
    $this->click('Login');
    $this->assertText('Sorry');
  }

  # make sure you can't get in with bad credentials
  function testLoginBadCredentialsFails() {
    $this->getBlankLoginForm();
    $this->setField('data[User][username]', 'FOO');
    $this->setField('data[User][password]', 'BAR');
    $this->click('Login');
    $this->assertText('Sorry');
  }

  /* REGISTRATION FORM TESTS */

  # this function is called by other functions to verify the
  # registration form is being displayed
  function verifyRegistrationFormDisplayed() {
    $this->assertText('Registration Form');
  }
  
  # this function is called by other functions to verify the
  # second registration form is being displayed
  function verifySecondRegistrationFormDisplayed() {
    $this->assertText('Registration');
    $this->assertText('Confirmation');
  }
  
  function verifyBlankRegistrationFormDisplayed() {
    $this->assertText('Registration Form');
    # assert these fields exist and are blank
    $this->assertField('data[User][username]', '');
    $this->assertField('data[User][email_address]', '');
    $this->assertField('data[User][password]', '');
    $this->assertField('data[User][password_confirm]', '');
  }

  function getBlankRegistrationForm() {
    $this->get($this->baseurl . "/users/register");
    $this->verifyBlankRegistrationFormDisplayed();
  }

  function testRegistrationEmptyFails() {
    $this->getBlankRegistrationForm();
    $this->click('Register');
    $this->verifyRegistrationFormDisplayed();
    $this->assertText('required');
  }

  function testRegistrationJustUsername() {
    $this->getBlankRegistrationForm();
    $this->setField('data[User][username]', 'FOO');
    $this->click('Register');
    $this->verifyRegistrationFormDisplayed();
    $this->assertText('required');
  }

  function testRegistrationValidations1() {
    $this->getBlankRegistrationForm();
    $this->setField('data[User][username]', 'FOO');
    $this->setField('data[User][email_address]', 'FOO');    # fails
    $this->setField('data[User][password]', 'FOO');         # fails
    $this->setField('data[User][password_confirm]', 'FOO'); # fails
    $this->click('Register');
    $this->verifyRegistrationFormDisplayed();
    $this->assertText('valid');
    $this->assertText('The Password');
  }

  # test that a valid registration attempt takes me to /users/register2
  function testRegistrationValid1() {
    $this->getBlankRegistrationForm();
    $this->setField('data[User][username]', 'bubba');
    $this->setField('data[User][email_address]', 'bubba@example.com');
    $this->setField('data[User][password]', 'aaaaaa');
    $this->setField('data[User][password_confirm]', 'aaaaaa');
    $this->click('Register');
    # test: should see the second registration form
    $this->verifySecondRegistrationFormDisplayed();
    # now clean up the database
    $this->delete_bubba_from_db();
  }

  /* TRY TO ACCESS APPLICATION PAGES WITHOUT LOGIN */
  
  # no access to /projects
  function testProjects() {
    $this->get($this->baseurl . "/projects");
    $this->assertText($this->loginText);
  }

  # no access to /reports
  function testReports() {
    $this->get($this->baseurl . "/reports");
    $this->assertText($this->loginText);
  }

  # no access to /users
  function testUsers() {
    $this->get($this->baseurl . "/users");
    $this->assertText($this->loginText);
  }

  # no access to /users/view/1
  function testUsersView1() {
    $this->get($this->baseurl . "/users/view/1");
    $this->assertText($this->loginText);
  }

  # do allow access to /pages/terms
  function testTerms() {
    $this->get($this->baseurl . "/pages/terms");
    $this->assertText('Terms');
  }

  # do allow access to /pages/privacy
  function testPrivacy() {
    $this->get($this->baseurl . "/pages/privacy");
    $this->assertText('Privacy Policy');
  }

  # allow access to '/'
  function testHomePage() {
    $this->get($this->baseurl . "/");
    $this->assertText('Welcome');
    $this->assertText('Home');
  }
  
  
  /* TEST VALID LOGINS, AND PAGES AFTER VALID LOGINS */

  function testLoginGoesToProjects() {
    $this->restart();  # restarts the browser as if a new session
    $this->get($this->baseurl . "/users/login");
    $this->assertField('data[User][username]', '');
    $this->setField('data[User][username]', 'testuser1');
    $this->setField('data[User][password]', 'aaaaaa');
    $this->click('Login');
    $this->assertText('Projects');
    $this->assertText('Project Type');
    $this->assertText('Description');
    $this->clickLink('logout');
  }

  function testChangePasswordBackAndForth() {
    echo("*** testChangePasswordBackAndForth can fail if test password gets out of sync ***\n");
    $this->getToChangePasswordForm();

    # this should generate validation error messages
    $this->setField('data[User][current_password]', 'aaaaaa');
    $this->setField('data[User][new_password1]', 'bbbbbb');
    $this->setField('data[User][new_password2]', 'bbbbbb');
    $this->click('Save');
    # should go back to /users/view
    $this->assertText('Account Information');

    # now go back and change password back to the default
    $this->clickLink('change password');
    $this->setField('data[User][current_password]', 'bbbbbb');
    $this->setField('data[User][new_password1]', 'aaaaaa');
    $this->setField('data[User][new_password2]', 'aaaaaa');
    $this->click('Save');
    # should go back to /users/view
    $this->assertText('Account Information');

    # and we're out of here
    $this->clickLink('logout');
  }

  # navigate to the "edit account info" form
  function getToEditAccountInfoForm() {
    $this->restart();  # restarts the browser as if a new session
    $this->get($this->baseurl . "/users/login");
    # login
    $this->setField('data[User][username]', 'testuser1');
    $this->setField('data[User][password]', 'aaaaaa');
    $this->click('Login');
    # should be at projects page
    $this->assertText('Projects');
    # go to account info
    $this->clickLink('account');
    $this->click('Edit Account Information');
    
    # (1) should now be looking at edit account info form; make blanks fail first
    $this->assertField('data[User][first_name]', '');
    $this->assertField('data[User][last_name]', '');
    $this->assertField('data[User][email_address]', '');  # this should make the form fail
    $this->click('Save');
    # should fail with an error message about the email address
    $this->assertText('Username');
    $this->assertText('valid email address');
    
    # (2) try duplicating my email address, should fail
    $this->assertField('data[User][first_name]', '');
    $this->assertField('data[User][last_name]', '');
    $this->assertField('data[User][email_address]', 'testuser@example.com');
    $this->click('Save');
    # should fail with an error message about the email address
    $this->assertText('Username');
    $this->assertText('in use');
    
    # (3) an invalid email address should also fail
    $this->assertField('data[User][first_name]', '');
    $this->assertField('data[User][last_name]', '');
    $this->assertField('data[User][email_address]', 'POOP');  # make it fail
    $this->click('Save');
    # should fail with an error message about the email address
    $this->assertText('Username');
    $this->assertText('in use');
    
    $this->clickLink('logout');
  }

}

?>

Running CakePHP web/integration tests from a browser

Although it's not a part of automated testing, a nice thing you can do with these CakePHP integration tests is you can run them from the browser. To do so, just go to the main 'tests' URL for your application, like this:

http://myapp:8888/test.php

then click "Test Cases", and then select the tests you want to run under "App Test Cases". Your tests are named after your class, so in my case my web test class is named CompleteWebTestCase, and the link is named "views/CompleteWeb".

Running CakePHP web/integration tests from the command line

I can also run these CakePHP web tests from my command line. In my project's root directory, I can run my CakePHP web tests like this:

prompt> cake testsuite app case views/complete_web

CakePHP SimpleTest web tests

I hope this brief tutorial on how to create and run CakePHP web tests with SimpleTest has been helpful. I've gone over this pretty fast, so if you have any questions about anything, just leave a note in the Comments section below. Also, if you're just interested in how I clean up my CakePHP database from my web tests, see my How to delete CakePHP test data from your database tutorial.