Using atkMockDb

From Achievo/ATK Wiki

Jump to: navigation, search

ATK Howto: Using atkMockDb

Complexity: Easy
Author: Dennis Luitwieler <dennis@ibuildings.nl>

List of other Howto's

Contents

Preface

In an ATK application where you do not have the luxury of always having a seperate database just for running automated tests on a live Environment (for instance WDE or Achievo), it is not safe to run testcases that actually add records to the database. Even though testcases can be built to clean-up testdata afterwards, this is not without risk! For instance, when a testcase breaks halfway through the code with a PHP parse error, any testdata that was already added will stay there. Besides the fact that this can break any future executions of your testcase, the testrecord stays in the live database and possibly break the live site.

This is where the atkMockDb kicks in :)

What is the atkMockDb?

The atkMockDb is a normal atkDb class but it doesn't actually execute any queries on the database. This prevents the problem from executing insert or update queries on a live environment, but it also prevents select queries from returning any data. Since ATK performs quite some database queries in order to execute it's functions, we need a way to simulate some results for any query that is executed. The atkMockDb provides just that!

How to use the atkMockDb

Configuring the atkMockDb

Create the testcase using the atkTestCase as its base class, so you can use the setMockDb() method to easily configure your testcases to use the atkMockDb.

Remember to also restore the original database, when you are done with the mock database, or other tests which depend on the regular database may fail!

require_once(atkconfig("atkroot")."atk/test/class.atktestcase.inc");
 
class test_mynode extends atkTestCase 
{
  public function test_asimpletestcase()
  {
    $this->setMockDb();            // tell ATK to use it
 
    if (atkGetDb()->m_type !== 'mock')
      exit('Testcase is not using the mockDb!! Exiting!');
      
    // some test stuff....
 
    $this->restoreDb();
  }
}

setResult

When you know the exact query that will be executed, you can set the records that will be returned by the mock database. If you need a certain record more often it is probably easy to create a method for it. Example:

/**
 * Build a default record for a given node.
 * 
 * Set the attributenames through the params argument.
 *
 * @param atkNode $node
 * @param Array $params
 * @return Array A record
 */
private function createRec($node, $params=array())
{
  $rec = $node->initial_values();
  
  foreach ($params as $key=>$value)
  {
    $rec[$key] = $value;
  }
  
  return $rec;
}

After that you can tell the mock database that it should return your dummy record in the following manner:

public function test_testanother()
{
  // Initialize the mockdb ....
  
  $node = atkGetNode('mymodule.mynode');  // We want a record for this node.
  $mockRec = $this->createRec($node, array('id'=>2, 'name'=>'test')); // create record
  
  // this is the query that we want to cause the dummy record to be returned.
  $sql= "SELECT * FROM mynodetablename WHERE id='2'";
  
  // tell atkMockDb to return the mockRec on the given query.
  atkGetDb()->setResult(array($mockRec), $sql);
 
  // rest of testcase ...
}


setRegexResult

Often you do not know the exact query that will be executed, or you know that the query might change a little. To prevent that you have to rewrite the query in your testcase for each small change (for instance when a new column is added to the node), you can use the setRegexResult method.
With setRegexResult you can provide a regular expression instead of a query. If the mockdb matches a query with a regular expression, it will return the record(s) you have told it to.

Example:

public function test_testathird()
{
  // Init mockDb ..., initialise the atkNode and create a dummy record ($mockRec) for it
  
  //Regardless of the questioned columns and regardless of wether the id field has quotes around it   
  $regex = '/^SELECT .*FROM mynodetablename WHERE id='?2'?/';
  atkGetDb()->setRegexResult(array($mockRec), $regex); // set the result
 
  // rest of testcase ..
}


The query history

Often ATK calls multiple queries automatically, i.e. when calling deleteDb() on a node. AtkMockDb remembers all the queries that are called, so you can perform checks on them afterwards. There is a method clearQueryHistory() that (as the name states) clears that history. You might want to use that from time to time to prevent the atkMockDb from checking the regular expressions against each and every query that is executed in your testcase method. Another example is probably in order:

public function test_testafourth()
{
  // .. init mockdb, create a record $mockRec, the node $node and a set any needed results.
  
  // update a field.
  $mockRec['name'] = 'newname';
 
  $this->assertTrue($node->validate($mockRec, 'update'));
  $db->clearQueryHistory(); //Clear the query history at first.
  $this->assertTrue($node->updatedb($mockRec, true));   // run  updateDb on the mock node.
 
  // Did ATK try to update the field.
  $regex = '/^UPDATE.*tablename.*SET.*name=\'newname\'.*WHERE.*tablename.id='2'.*/';  
  $result = $this->checkQueryByRegexp(atkGetDb()->getQueryHistory(), $regex);
  $this->assertTrue($result);
 
  // rest of testcase ...
}
 
/**
 * Check if a certain query was executed by comparing with a regular expression in
 * a list of queries.
 *
 * @param Array $queries
 * @param String $regexp A regular expression
 * @return Boolean True if a match was found, false otherwise
 */
private function checkQueryByRegexp($queries, $regexp)
{
  foreach ($queries as $query)
  {
    $result = preg_match($regexp,$query);
    if ($result !== 0)
      return true;
  }
  atkdebug('No queries matched the reqular expression.');
  return false;
}


Final note

Note that using atkMockDb is not foolproof, it doesn't guarentee that the syntax of the queries is correct and that mysql will properly execute the query, you only check if ATK even tries to execute the queries.

Testcases for a certain function should only test that function. Keep the responsibilities to test if (for instance) atkNode::updateDb() is executed properly, to the testcases for atkNode.

Also, remember that when you use the atkMetaNode extra care is needed because the ATK can't build any atkMetaNodes when you are using the mockDb

Personal tools
Navigation