TDDing the “Gattiny” plugin – 04

Dealing with system requirements in an acceptance test.

Part of a series

This is the fourth post in a series of posts chronicling my attempt to use test-driven development techniques to develop the “Gattiny” WordPress plugin.
The plugin will take care of resizing animated GIF images preserving the animations when uploading them through the WordPress Media screen.
The first post is probably a better starting point than this one.

What if Imagick is not installed?

I’ve left the code of the plugin in a state where the three functional tests have been passed and I have the code needed to nail the work idea; it’s now to time to refine and improve the code, in refactoring and restructuring terms, to make it communicate properly with its surroundings and gracefully integrate with WordPress.
The plugin animated GIF resizing capabilities are the center and scope of it, yet the foundation of this capability is the presence of the Imagick extension; WordPress itself will not resize images on upload if Imagick or GD extensions are not installed on the server and will not provide any UI feedback about it: it either resizes images on upload or it doesn’t.
As a user installing a plugin with the right to ignorance I expect to be provided some feedback about my site supporting the plugin features or not.
To model this “feedback” I would like the plugin to show the user a notice when activating the plugin, and prevent the plugin activation itself from happening, if the Imagick extension is not loaded.
The “scenario”, to use Behaviour-driven development terms, for this would be this:

As a user that has downloaded and installed Gattiny Given the Imagick extension is not loaded on the server When I activate the Gattiny plugin Then I should see a notice telling me it has not been activated as its functions are not supported 

Simple enough in terms of descriptions but deceitful in terms of how to write a test for this.

Can I mock PHP extensions? Should I?

Sounds like an acceptance test having a user activating the plugin, with no knowledge of the plugin internals, and being presented a notice.
Yet, scratching the surface, the second line proves not to be that trivial:

Given the Imagick extension is not loaded on the server

The “server” will be, in the case of my local test environment and a future CI integration, the same powering all the tests and using the command line to activate and deactivate an extension, the Imagick one in the case of the test in question. To run the test is overreaching in my opinion.
Since I want now to write an acceptance test for this, I scaffold the first test Cept:

wpcept generate:cept acceptance UnsupportedNotice 

And write the following code in the test:

<?php $I = new AcceptanceTester($scenario); $I->wantTo('activate the plugin on site that does not support it'); $I->haveOptionInDatabase('active_plugins', []); $I->haveOptionInDatabase('gattiny_supported', 0); $I->loginAsAdmin(); $I->amOnPluginsPage(); $I->activatePlugin('gattiny'); $I->seeElement('.gattiny_Notice--unsupported'); $I->seePluginDeactivated('gattiny'); 

To note here, beside the mere translation of the scenario above in Cept format, are the two haveOptionInDatabase lines.
The first is needed to make sure Gattiny is not active by the time the acceptance test hits the plugin page; setting the active_plugins array to an empty one deactivates them all (although not gracefully). This is needed because the starting SQL dump for the tests lists the Gattiny plugin as active: instead of having to explicitly activate it in each test I’m explicitly deactivating it here.
The second instruction, $I->haveOptionInDatabase('gattiny_supported', 0);, inserts the gattiny_supported option in the database and sets its value to 0; this looks like a “trick” to get around the issue of the possibly not loaded Imagick extension but really is a “trick” I’ve used many times.
An option, filterable and accessible to third party code as well, allows a finer grain control over the plugin functions and activations and demonstrates, yet another time, that testing requires wider thinking about the problems.
Sure Gattiny should be deactivated when the Imagick extension is not loaded but is this the only case? I can think of others:

  • Imagick version is not the minimum required one
  • the plugin conflicts with another
  • a future version of WordPress might include the function the plugin is currently providing, let WordPress do WordPress
  • further reasons

The option based choice leaves more doors open for the plugin to evolve its behaviour in the future.
When it comes to it the only thing Gattiny cares about is: can I run or not?

Passing the acceptance test
Passing the acceptance test

The updated code

Here is the code updated to pass the tests (the gattiny_GifEditor class is unchanged):

<?php /* Plugin Name: Gattiny Plugin URI: https://wordpress.org/plugins/gattiny/ Description: Resize animated GIF images on upload. Version: 0.1.0 Author: Luca Tumedei Author URI: http://theaveragedev.com Text Domain: gattiny Domain Path: /languages */ add_action('admin_init', 'gattiny_maybeDeactivate'); function gattiny_maybeDeactivate() { $plugin = plugin_basename(__FILE__); if (!empty($_GET['activate']) && is_plugin_active($plugin) && current_user_can('activate_plugins')) { return; } if ('0' === get_option('gattiny_supported')) { unset($_GET['activate']); add_action('admin_notices', 'gattiny_unsupportedNotice'); deactivate_plugins(plugin_basename(__FILE__)); } } function gattiny_unsupportedNotice() { deactivate_plugins(plugin_basename(__FILE__)); ?> <div class="notice notice-error gattiny_Notice gattiny_Notice--unsupported"> <p><?php _e('Gattiny is not supported by your server!', 'gattiny'); ?></p> </div> <?php } add_filter('wp_image_editors', 'gattiny_filterImageEditors'); function gattiny_filterImageEditors(array $imageEditors) { require_once dirname(__FILE__) . '/src/GifEditor.php'; array_unshift($imageEditors, 'gattiny_GifEditor'); return $imageEditors; } 

The code is on GitHub tagged post-04.

Next

Moving down to integration testing I will write tests to make sure that gattiny_supported option is properly set “reading” the environment, then move to the plugin core functions.

I appreciate your input