Drupal, SimpleTest, and the node access API

Setting up Simpletest and Drush on Drupal 6.x:

  1. Download and enable Simpletest with drush dl simpletest; drush en -y simpletest
  2. Download simpletest.drush.inc to your ~/.drush/drush_extras directory. This version allows you to run a single test from the command-line.
  3. Create a custom module with a tests/ subdirectory, and write your tests in it. (See this Lullabot Simpletest tutorial.)

We’re starting another Drupal project. While the IT architect is working on clarifying the requirements, I volunteered to implement the risky parts so that we could get a better sense of what we needed to do.

The first major chunk of risk was fine-grained access control. Some users needed to be able to edit the nodes associated with other users, and some users needed to have partial access to nodes depending on how they were referenced by the node. Because there were many cases, I decided to start by writing unit tests.

SimpleTest was not as straightforward in Drupal 6.x as it was in Drupal 5.x. There were a few things that confused me before I figured things out.

I wondered why my queries were running off different table prefixes. I didn’t have some of the data I expected to have. It turns out that Simpletest now works on a separate Drupal instance by default, using a unique table prefix so that it doesn’t mess around with your regular database. I’m doing this on a test server and I want to be able to easily look up details using SQL, so I needed to add this to my test case:

class ExampleTestCase extends DrupalWebTestCase {
  function setUp() {
    global $base_url;
    $this->originalPrefix = $GLOBALS['db_prefix'];
  }
  function tearDown() { }
}

I also didn’t like how the built-in $this->drupalCreateUser took permissions instead of roles, and how it created custom roles each time. I created a function that looked up the role IDs using the {role} table, then added the role IDs and roles to the $edit['roles'] array before creating the user.

Lastly, I needed to add the Content Profile operations to my custom user creation function. I based this code on content_profile.test.

$this->drupalLogin($account);
// create a content_profile node
$edit = array(
  'title' => $account->name,
  'body'  => $this->randomName(),
);
$this->drupalGet('node/add');
$this->drupalPost('node/add/' . str_replace(' ', '-', $role), $edit, t('Save'));

It would’ve been even better to do this without going through the web interface, but it was fine for a quick hack.

I had the setup I wanted for writing test cases that checked user permissions. I wrote functions for checking if the user could accept an invitation (must be invited, must not already have accepted, and must be able to fit). SimpleTest made it easy to test each of the functions, allowing me to build and test blocks that I could then put together.

The code in content_permission.module turned out to be a good starting point for my field-level permissions, while the Drupal node access API made it easy to handle the user-association-related permissions even though I used node references instead of user references.

It was a good day of hacking. I wrote tests, then I wrote code, then I argued with the computer until my tests passed. ;) It was fun seeing my progress and knowing I wasn’t screwing up things I’d already solved.

If you’re writing Drupal code, I strongly recommend giving SimpleTest a try. Implementing hook_node_access_records and hook_node_grants is much easier when you can write a test to make sure the right records are showing up. (With the occasional use of node_access_acquire_grants to recalculate…) Otherwise-invisible Drupal code becomes easy to verify. The time you invest into writing tests will pay off throughout the project, and during future work as well. Have fun!

  • http://www.comm-press.de/ Karsten Frohwein

    Hi,

    just to mention that the Lullabot Simpletest guide by webchick is just too old!

    Please take a look at:

    http://drupal.org/simpletest
    http://drupal.org/project/examples
    http://www.comm-press.de/en/blog/simple-test-drupal

  • http://sachachua.com Sacha Chua

    Karsten: Thanks for dropping by with the update! =D

  • http://advomatic.com dalin

    When I need to setup a test using pre-existing data, I’ve done the following:

    variable_orginal_set(‘cd_sunlight_api_key’);
    }

    /**
    * Set the given variable using the value from the base installation.
    *
    * @param string $name
    *
    * @return mixed
    * The value.
    */
    function variable_orginal_set($name) {
    // Note the lack of table prefixing to pull the variable from the base installation.
    $value = unserialize(db_result(db_query(
    ‘SELECT value FROM variable WHERE name = “%s”‘, $name)));
    $this->verbose(“$name: “. $this->_var_export($value));
    variable_set($name, $value);
    return $value;
    }
    }
    ?>

    This assumes that you don’t normally use table prefixing.