TDDing the "Gattiny" plugin - 01

Developing a WordPress plugin from scratch with Codeception and wp-browser.

Gattiny

The plugin name is a brilliant idea of an italian colleague mixing the Italian translation of the word “kittens” (“gattini”) with a touch of Englishness replacing the trailing “i” with an “y”.
The purpose of this plugin will be to resize animated GIF images during upload preserving animations and creating different sized versions for each applicable image size.
As any idea of mine it could crash and burn; it provides, though, an excellent playing ground for test-driven development and I simply cannot let the chance slip.

Setting up for testing

Starting from scratch I’ve created the plugin folder in my repositories folder and initialized git:

cd ~/Repos
mkdir gattiny
git init

As customary in my development flow I’m symbolically linking the plugin folder in the WordPress installation folder:

ln -s ~/Repos/gattiny ~/Sites/wp/wp-content/plugins/

After that I init Composer answering the following questions and ending up with this file:

composer init

I’m then putting in place the plugin main file, nothing but WordPress required comment block so far, to allow WordPress to pick it up as an available plugin.

How does it work now?

Before I write any code I’m going to test current WordPress handling and support of animated GIFs to look for any shortcomings.
WordPress will create a version of the uploaded images only if the Imagick or gd are installed and preferes the former.
Since WordPress itself will not provide support for image resizing on upload without the extensions installed, I’m assuming the plugin should work when the Imagick extension is activated.
The current state of support provided by WordPress to animated GIFs is not to modify the original image but to create resized static versions of it when scaling; any version of the uploaded GIF created by WordPress will hence be a resized, though not animated, version of the first image in the GIF stack.

Asserting the obvioius

The series is about test-driven development and as such I will try to write applicable tests before I write any code.
My usual pick for a first test is an acceptance one, but this specific case requires some thinking: what am I going to test first?

  • when uploading a GIF animated image a resized version of it should be created in the WordPress uploads folder
  • when uploading a GIF animated image each resized version should still be an animated image

looking at the wording of the two sentences above it’s easy to spot how the tests, to assert their conditions, must know and check on the functionality implementation details. This is a white-box approach to the problem that deviates from the realm of acceptance testing.
My first test will then be a functional one.

Bootstrapping the tests

Once again the quickest way to get up and running with the tests is to use wp-browser and its interactive bootstrap command:

composer require --dev lucatume/wp-browser
wpcept bootstrap --interactive

Note: prepend vendor/bin to your path to avoid having to type ./vendor/bin/wpcept every time.

Functional tests, as well as acceptance tests, require a starting database fixture to be set up; that database fixture will be:

  • a clean and empty WordPress installation
  • the Gattiny plugin activated
  • no other plugin or theme active on the site (save for the default theme)

To set up the database fixture I’m using wp-cli from the local WordPress installation folder (/home/luca/Sites/wp in my case):

wp site empty --yes
wp plugin deactivate $(wp plugin list --status=active --field=name)
wp plugin activate gattiny
wp db export ~/Repos/gattiny/tests/_data/dump.sql

Since the functional suite has been configured during the interactive bootstrap process, I’m now able to run the empty suite successfully using wpcept run functional

[caption id=“attachment_3387” align=“aligncenter” width=“717”]Gattiny functional testing - dry run Gattiny functional testing - dry run[/caption]

Time to write the first test.

Scaffolding

When writing functional tests I usually use Codeception cest format to re-use commong _before and _after statements in each test method:

wpcept generate:cest functional FormatCreation

After some tweaking, this is the first version of the FormatCreationCest.php file; it’s doing nothing but asserting the obvious here:

<?php


class FormatCreationCest {
    protected $gif = 'images/kitten-animated.gif';
    protected $jpg = 'images/kitten-image.jpg';
    protected $uploads;
    protected $sizes = [
        'thumbnail'    => '150x150',
        'medium'       => '300x169',
        'medium_large' => '768x432',
        'large'        => '1024x576',
        'full'         => ''
    ];

    public function _before( FunctionalTester $I ) {
        $config        = \Codeception\Configuration::config();
        $this->uploads = $config['folders']['uploads'] . '/' . date( 'Y/m' );
        $I->useTheme( 'empty' );
    }

    public function _after( FunctionalTester $I ) {
        $I->deleteDir( $this->uploads );
    }

    /**
     * It should create a version of the gif for each size
     * @test
     */
    public function create_a_version_of_the_gif_for_each_size( FunctionalTester $I ) {
        $I->loginAsAdmin();
        $I->amOnAdminPage( 'media-new.php' );
        $I->attachFile( 'input[name="async-upload"]', $this->gif );
        $I->click( 'input[name="html-upload"]' );

        $I->seeResponseCodeIs( 200 );

        $I->amInPath( $this->uploads );
        foreach ( $this->sizes as $key => $size ) {
            $suffix = '' !== $size ? '-' . $size : '';
            $I->seeFileFound( basename( $this->gif, '.gif' ) . $suffix . '.gif' );
        }
    }
}

The test code requires some explanation to understand what is happening.
I’m using the WPBrowser module in place of the WordPress one to submit the file upload requests successfully; I’ve changed the functional suite configuration to use it along with the Filesystem module and the WPDb one:

# Codeception Test Suite Configuration

# Suite for WordPress functional tests.
# Emulate web requests and make the WordPress application process them.


class_name: FunctionalTester
modules:
    enabled:
        - \Helper\Functional
        - Filesystem
        - WPDb
        - WPBrowser
    config:
        WPDb:
            dsn: 'mysql:host=localhost;dbname=wp'
            user: root
            password: root
            dump: tests/_data/dump.sql
            populate: true
            cleanup: true
            url: 'http://wp.dev'
            tablePrefix: wp_
        WPBrowser:
            url: 'http://wp.dev'
            adminUsername: admin
            adminPassword: admin
            adminPath: /wp-admin


In the _before method I’m setting the theme to empty; the empty theme lives in the _data/empty-theme folder and is, as the name implies, nothing but an empty theme.
The purpose here is to use a theme I control and that’s not modifying WordPress default image sizes in numbers and quality in any way for the tests.
Using the Copier extension the theme will be copied into the WordPress installation themes folder before the tests and removed afterward.

actor: Tester
paths:
    tests: tests
    log: tests/_output
    data: tests/_data
    helpers: tests/_support
settings:
    bootstrap: _bootstrap.php
    colors: true
    memory_limit: 1024M
extensions:
    enabled:
        - tad\WPBrowser\Extension\Copier
    config:
        tad\WPBrowser\Extension\Copier:
            files:
                tests/_data/empty-theme: /home/luca/Sites/wp/wp-content/themes/empty-theme
folders:
    uploads: /home/luca/Sites/wp/wp-content/uploads

Lastly I’m providing the absolute path to the WordPress local installation root uploads folder in the configuration under the folders key; this is an handy trick offered by Codeception configuration that allows me to access that information using a service locator in my tests as I’m doing in the lines:

$config        = \Codeception\Configuration::config();
$this->uploads = $config['folders']['uploads'] . '/' . date( 'Y/m' );

Running the only test results in a success:

[caption id=“attachment_3388” align=“aligncenter” width=“874”]Gattiny functional testing - first green Gattiny functional testing - first green[/caption]

Next

The code I’ve shown here is on GitHub tagged post-01 for anyone to see.
Next step will be writing the second functional test and get down to work.