Skip to content

Instantly share code, notes, and snippets.

@luciferous
Created November 4, 2010 23:26
Show Gist options
  • Save luciferous/663397 to your computer and use it in GitHub Desktop.
Save luciferous/663397 to your computer and use it in GitHub Desktop.
Mocks for Drupal functions

This is an idea for creating mocks for functions that could be disrupted by events in the real world, events like database connectivity or some other IO. The mocks should call the original function everywhere except during testing. The following is an example code snippet that uses variable_get, which would be thoroughly disrupted if it could not connect to the database.

function my_function() {
  $my_var = variable_get('my_var');
  // Do something with my_var ...
}

There's no easy way to test my_function without a database, because variable_get needs the database. One way around this is to allow passing variable_get as an argument:

function my_function($variable_get = 'variable_get') {
  $my_var = $variable_get('my_var');
  // Do something with my_var ...
}

Then all that is required to test my_function is to pass a stub for variable_get. The usual callers of my_function will be using the original variable_get, but tests can call my_module_function('mock_variable_get').

To prepare $mock_variable_get, we would need to create a function and use it like:

function mock_variable_get() {
  // Simulate the database response
}

my_function('mock_variable_get');

This is not ideal: different tests may want to mock variable get with differing responses. A better solution is to prepare the mock along with the test that uses it. Something like this:

$mock_variable_get = new MockFunction::generate('variable_get');
$mock_variable_get->setReturnValue(
  'Some value for my_var',
  array('my_var')
);

Then we can call my_function and pass it the MockFunction object.

$mock_variable_get = new MockFunction::generate('variable_get');
$mock_variable_get->setReturnValue(
  'Some value for my_var',
  array('my_var')
);
my_function($mock_variable_get);

We can also use the mock as a critic by preparing it to expect to receive a specific set of arguments:

$mock_variable_get->expectOnce(array('some obnoxious arg'));
my_function($mock_variable_get); // Assertion fails here
<?php
class MockFunction {
public $func;
public $returns;
private function __construct($func) {
$this->func = $func;
$this->returns = array();
}
function setReturnValue($return, array $args = array()) {
$this->returns[serialize($args)] = $return;
}
function expectOnce($args = array()) {
// TODO Check expectations
}
function __destruct() {
// TODO Check expectations
}
function __invoke() {
$args = func_get_args();
$key = serialize($args);
$catchall = serialize(array());
return
(
!array_key_exists($key, $this->returns)
&& array_key_exists($catchall, $this->returns)
)
? $this->returns[$catchall]
: $this->returns[$key];
}
static function generate($func) {
static $callable;
if (is_null($callable)) $callable = is_callable(new self(''));
return $callable
? new self($name)
: create_function('',
sprintf(
'static $m;
if (!$m) $m = new %s(\'%s\');
return $m->__invoke(func_get_args());',
__CLASS__, $name
)
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment