WP Queries module – 00

Asserting WordPress database queries in tests.

An example

The need might arise to test and verify that some interaction with the WordPress database API, through transients for example, did happen as and when intended.
Considering the class below:

class TransientCache{

    public $transient = 'someClass_cache';
    private $cache = false;

    private function prime_cache(){
        if($this->cache === false){
            $cache_transient = get_transient($this->transient);
            if(false === $cache_transient){
                $this->cache = array();
            } else {
                $this->cache = $cache_transient;
            }
        }
    }

    public function fetch($key, $default = ''){
        $this->prime_cache();

        return isset($this->cache[$key]) ?
            $this->cache[$key] :
            $default;
    }

    public function store($key, $value){
        $this->prime_cache();

        $this->cache[$key] = $value;
    }

    public function is_primed(){
        return false !== $this->cache;
    }

    public function __destruct(){
        set_transient($this->transient, $this->cache);
    }
}

The code is simple at this point but I’d like to put some regression testing in place for future iterations.
Two test methods I came up with are the following:

public function test_will_prime_cache_on_first_fetch_request(){
    $cache = new TransientCache();

    // let's make sure it's not primed upon instantiation
    $this->assertFalse(get_transient($cache->transient));

    // make a fetch request
    $cache->fetch('someKey');

    // let's make sure the priming did not update the transient yet
    $this->assertFalse(get_transient($cache->transient));

    $this->assertTrue($cache->is_primed());
}

public function test_will_not_get_transient_again_primed(){
    $cache = new TransientCache();

    set_transient($chache->transient, array('foo' => 'bar'));

    $this->assertEquals('bar', $cache->fetch('foo'));

    set_transient($chache->transient, array('foo' => 'baz'));

    $this->assertEquals('bar', $cache->fetch('foo'));
}

The above methods would be part of a test case extending the wp-browser provided WPTestCase class: this will allow them to tap into WordPress defined functions, like the transient related ones above, at runtime.

Checking for the queries

In the end the purpose of this cache implementation is not to make a SELECT query every time the fetch method is called and to run a single UPDATE or INSERT query when the cache instance is destroyed during the garbage collection.
The current version of the code is simple enough but the class might evolve in the future and I’d like to put some regression testing in place to nail the foundations of the class (as little database queries as possible) whatever extension or modification I might make in the future.
To assert this is how the class is currently working in its WordPress database interaction and is supposed to keep working I wrote the test below:

public function test_will_run_one_select_to_fetch_the_transient(){

    // let's make sure wpdb is registering each query
    if(!defined('SAVEQUERIES')){
        define('SAVEQUERIES', true);
    }

    global $wpdb;

    // reset the query register
    $wpdb->queries = [];

    $cache = new TransientCache();

    $cache->fetch('key'); 
    $cache->fetch('key'); 
    $cache->fetch('key');
    $cache->fetch('key2'); 
    $cache->fetch('key2'); 
    $cache->fetch('key2');

    // get the queries registered by wpdb
    $queries = $wpdb->queries;

    // filter the queries keeping those...
    $filtered_queries = array_filter($queries, function(array $query){
        $select_pattern = '/^select.*/i';
        $method_pattern = 'TransientCache->fetch';

        // ...that start with a SELECT statement...
        return preg_match($select_pattern, $query[0])
            // ...and are triggered by the method
            && false !== strpos($query[2], $method_pattern);
    });

    $this->assertCount(1, $filtered_queries)
}

This method is not overly complicated and relies on the SAVEQUERIES constant to work.
The part of the test method setting the SAVEQUERIES constant could be moved to the setUpmethod or in a separate method and the part dealing with the queries assertion could be generalized and reused elsewhere.
But what if it all came baked into a Codeception module?

Enter WPQueries

I’m developing a Codeception module that will be added to the wp-browser package that aims at making tests like the one above less of a wheel reinveinting process.
After including the module in the suite (e.g. the typical wpunit one):

modules:
    enabled:
        - WPLoader
        - Helper\WPunit
        - WPQueries

the test_will_run_one_select_to_fetch_the_transient method could be rewritten like this;

public function test_will_run_one_select_to_fetch_the_transient(){

    // WPQueries has set the `SAVEQUERIES` constant already

    $cache = new TransientCache();

    $cache->fetch('key'); 
    $cache->fetch('key'); 
    $cache->fetch('key');
    $cache->fetch('key2'); 
    $cache->fetch('key2'); 
    $cache->fetch('key2');

    // `$this->queries()` gets hold of the `WPQueries` module
    $this->queries()->assertQueriesCountByStatementAndMethod(
        1,
        'SELECT', 'TransientCache','fetch');
}

More methods will be available on the module and its nature will allow for it to be used anywhere a globally defined $wpdb instance is defined.
In the example above the method is working in conjunction with the WPLoader module but something similar could be done in an acceptance tests relying on the WPBootrapper module.
More updates to come.

I appreciate your input