Forcing SimpleTest to use the live database

Feb
23
By Morbus Iff | Filed Under: Drupal, SimpleTest

SimpleTest, the test suite that Drupal started using, and then improved upon, has primarily been used to test modules in their own little sandbox, unaffected by the outside world, user data, or client-desired tweaks. This is perfectly fine when you're working on a controlled piece of code, like a module intended for release. When you're building a client site, however, you often have a much more ephemeral set of quality assurances to make: that this CCK node has seven fields, that this field doesn't show up for that particular user role, that "Body" has been renamed to "Description", or that the front page display has a certain set of blocks.

Each of those case scenarios all involve changes to the "in-progress" database, the one where the client is adding content, or Views are being configured, or blocks are being made and placed, et cetera. Since SimpleTest's default goal is to start fresh for each particular test method, creating new database tables that have no content in them, you'd never be able to test any of the tweaks the client wants on their live site. If someone accidentally deleted a field from a previously perfect content type, SimpleTest wouldn't catch it.

Thankfully, we can fix this by overriding the setUp() and tearDown() methods that our test classes inherit from DrupalWebTestCase - these methods normally handle the creation of the fake database and the cleanup of any fake created data. (Note: this assumes you're using SimpleTest 6.x-2.x or Drupal 7: earlier versions will not work.)

<?php
class FunctionalGenericTestCase extends DrupalWebTestCase {
  function
getInfo() {
    return array(
     
'name' => t('Generic functionality'),
     
'description' => t('Generic user functionality tests for custom code.'),
     
'group' => t('Trellon Development'),
    );
  }

  // for our functional testing, we want to test the pages and code that
  // we've been generating in the real database. to do this, we need to
  // ignore SimpleTest's normal fake database creation and fake data
  // deletion by overriding it with our own setUp and tearDown. NOTE that
  // if we make our own fake data, we're responsible for cleaning it up!
 
function setUp() {
   
// support existing database prefixes. if we didn't,
    // the prefix would be set as '', causing failures.
   
$this->originalPrefix = $GLOBALS['db_prefix'];
  }
  function
tearDown() { }

  // ensure items from mocks exist.
 
function testFrontpageChanged() {
   
$this->drupalGet('');
   
$this->assertNoText(t('Welcome to your new Drupal website!'),
     
t('Default Drupal front page has been changed.'));
  }

  // check for all end-user fields.
 
function testContentTypeFields() {
   
$this->drupalLogin((object)array('name' => 'authed', 'pass_raw' => 'ahem'));

    $this->drupalGet('node/add/story');
   
$this->assertNoRaw('<label for="edit-body">Body: </label>',
     
t('"Body" renamed to "Story" per content-type-outline.pdf (2009-02-18).'));
  }
}
?>

As suggested in the comments, by overriding tearDown() we are now responsible for cleaning up any fake data that our tests create. To lessen the effects of this, we plan to create a number of standard test users - one for each client-required user role - that we can use drupalLogin() to become, as opposed to running a drupalCreateUser with a custom set of permissions (where we would then be responsible for deleting the created user and the role). We don't see this as anything too upsetting: these types of tests are all per-client anyways, so while we hope for some reuse (such as the useless default front page test above), the benefits of using SimpleTest for functionality tests such as this outweigh them.

Since the setUp() and tearDown() methods are per-class, we still have the ability to test any custom modules (or complex algorithms, etc.) in a fresh/sandbox environment - we'd just define a new testing class and leave out the overrides.

Note that this approach still emphasizes data and structure over actual display: tests would happily claim that Body has been renamed to Story, but wouldn't be able to tell us that an errant piece of CSS has caused that entire input field to be hidden from view. A human would still have to manually eyeball display issues (caused by CSS, etc.), as well as test any JavaScript functionality.

7 Comments

Just what we were looking

Just what we were looking for.

We need to run a test - which send data over SOAP to an external app.

The client then needs to perform some action the the data.

Then we run another test to suck the data back in.

We need the data to be persistent between tests so this is a perfect solution.

this method doesnt

this method doesnt work

Status    Group      Filename          Line Function                           
--------------------------------------------------------------------------------
Fail      Other      run-tests.sh       366 simpletest_script_run_one_test()  
    The test cannot be executed because it has not been set up properly.
Fail      Other      run-tests.sh       366 simpletest_script_run_one_test()  
    The test cannot be executed because it has not been set up properly.

It's a bit different in

It's a bit different in Drupal 7, instead of this:

<?php

 
function setUp() {
   
$this->originalPrefix = $GLOBALS['db_prefix'];
  }
?>
do this:
<?php
 
function setUp() {
   
$this->setup = TRUE;
  }
?>

Hello, I'm using Drupal 7.10

Hello,

I'm using Drupal 7.10 with core simpletest module.
I wrote a test:

<?php
class NodeTestTestCase extends DrupalWebTestCase {
  function
getInfo() {
    return array(
     
'name' => t('test name'),
     
'description' => t('test desc'),
     
'group' => t('test group'),
    );
  }

  function
setUp() {
   
$this->setup = TRUE;
  }

  function
tearDown() { }

  function
testFooBar() {
   
$this->drupalLogin((object)array('name' => 'foo', 'pass_raw' => 'bar'));
  }
}
?>

If test run, the next fatal error message displayed:

An AJAX HTTP error occurred. HTTP Result Code: 500 Debugging information follows. Path: /batch?id=34&op=do StatusText: Service unavailable (with message) ResponseText: DatabaseConnectionNotDefinedException: The specified database connection is not defined: simpletest_original_default in Database::openConnection() (line 1655 of /includes/database/database.inc).

What's wrong?

I'm tested with the example

I'm tested with the example code, but just the same error:

<?php
function setUp() {
 
// support existing database prefixes. if we didn't,
  // the prefix would be set as '', causing failures.
 
$this->originalPrefix = $GLOBALS['db_prefix'];
}
?>

For D8, you will also need to

For D8, you will also need to define the services container.

<?php
  
function setUp() {
 
$this->setup = TRUE;
 
$this->container = Drupal::getContainer();
}

function
tearDown() {

}
?>

In Drupal 7 to set correct

In Drupal 7 to set correct path for "Verbose message" links you should use the following:

  function setUp() {
    $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files');
    $this->public_files_directory = $this->originalFileDirectory . '/simpletest/SOME_FOLDER';
    $this->setup = TRUE;
  }

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.

More information about formatting options