Writing WooCommerce Extensions with Confidence

If you haven’t heard, Liquid Web is now the first company offering Managed WooCommerce hosting, which is a huge step forward in the world of WordPress-oriented e-commerce. As a result, I’ve been spending a lot of time over the last few weeks working on WooCommerce extensions that help improve the experience and performance of WooCommerce.

Screenshot of the Liquid Web Managed WooCommerce Hosting pageOne of the main WooCommerce extensions I’ve been working on is WooCommerce Custom Orders Table, which takes the WooCommerce 3.x CRUD concept to its next logical point: storing order data in a custom, flat table instead of scattered throughout post meta. Mindsize worked with other members of my team at Liquid Web to build the initial version of the plugin, then I came in to fix a few bugs.

It started with a callback not being registered properly. Hoping to prevent regressions in the future, I used WP-CLI to scaffold some PHPUnit testing for the plugin, then went to work fixing the issue. Next, I discovered that there were some improperly-prepared database queries, so I would start writing tests and refactoring. It seemed every thread I pulled unravelled another set of issues, and I was exploring the codebase (both the plugin and WooCommerce itself) by writing tests.

Finally, I reached the point where I needed to be able to generate new products and orders in my tests. “This has got to be a problem that’s already been solved,” I thought as I dug into WooCommerce core to see how they were handling test data. As it turns out, WooCommerce eschews the typical WordPress factories (e.g. $this->factory()->post->create()) in favor of a series of helper classes with static methods.

In WooCommerce core, generating an order might look something like this:

While it doesn’t offer quite the same level of flexibility as the default WordPress factories, this is a perfectly valid way to quickly generate a new order for test purposes. Now, if only there was a way to use these helpers in my tests… 🤔

Putting the “WooCommerce” into “WooCommerce Extensions”

The more I looked at how WooCommerce was handling its tests, the more I realized how much effort I was duplicating by trying to tie my plugin’s test bootstrap into WooCommerce. WooCommerce Custom Orders Table fundamentally alters the way that WooCommerce handles order data, so testing the plugin in isolation makes little sense; if something works in WooCommerce without the plugin, it should still work when the plugin is active.

I decided to take a new approach to testing the plugin: extending WooCommerce’s core test suite. Thanks to Composer, I could easily load WooCommerce as a development dependency:

Next, I popped open the composer.json file to ensure that WooCommerce’s core tests were included in the Composer-generated autoloader:

Pay close attention to the “extra” node at the bottom there: WooCommerce ships with the composer/installers package by default, so Composer will attempt to install WooCommerce to ./wp-content/plugins/woocommerce by default, instead of the traditional ./vendor/{vendor}/{name}structure.

After running composer update, Composer generates a fresh copy of the autoloader that includes all of the classes from WooCommerce’s tests/framework/ directory.

Extending the WooCommerce test suite

Now that we have access to WooCommerce from within our local project, it’s time to leverage it in our tests!

I’d recommend starting by either writing a new base test case or updating your test cases to extend the WC_Unit_Test_Case class. Either way, your test files will look something like this:

Extending WC_Unit_Test_Case ensures that your tests will get the same setup/tear-down treatment as the rest of WooCommerce, saving you a lot of unnecessary bootstrapping logic.

Speaking of bootstrapping, the last file we need to edit is the PHPUnit bootstrap file (tests/bootstrap.php, by default). I’ve added some extra to the WooCommerce Custom Orders Table plugin, but the general idea is:

  1. Determine the system paths for the test installation of WordPress and the WooCommerce tests/bootstrap.php file.
  2. If either of those are missing, throw an error and exit with a non-zero exit code.
  3. Load the WordPress core test framework’s include/functions.php file, which grants access to the tests_add_filter() method. This will be used to manually load your WooCommerce extension on “muplugins_loaded”.
  4. Require the Composer-generated autoloader, then finally the WooCommerce core test bootstrap file.

Put together, your WooCommerce extension’s bootstrap file may look something like this:

Running the tests

At this point, you should be able to run your test suite, with full access to WooCommerce functionality and test helpers. Running PHPUnit as you would normally, a test WordPress instance with WooCommerce should be spun up, and your tests executed. If that’s all you’re looking for, congratulations!

What if you need to test how WooCommerce behaves when your plugin is active, though? In our example, we’re fundamentally altering the way that WooCommerce handles orders; wouldn’t it be nice to know if we broke things elsewhere within WooCommerce?

This is where PHPUnit’s test suites come in handy: By defining two separate suites, we can separate our WooCommerce extension tests from WooCommerce core. In our phpunit.xml file, we can define multiple <testsuites />, which can then be addressed by name:

Now, PHPUnit can be run using only the plugin tests, only the WooCommerce core tests, or with all tests:

Making WooCommerce work for you

One of the beautiful things about extending the core test suite in our WooCommerce extensions is that we have a figurative “canary in the coal mine” if anything breaks in future releases of WooCommerce. If a filter we’re relying on is removed, or some method is rewritten in a way we hadn’t anticipated, our tests will let us know before our users run into troubles.

There’s another side to this reliance on the core test suite, too: it motivates you to contribute back to WooCommerce. In the last few weeks, I’ve spent more time digging through WooCommerce core than I’d ever thought possible. In some cases, I found areas that are important to the WooCommerce Custom Orders Table plugin, but simply didn’t have any test coverage within WooCommerce. Instead of writing more tests in the plugin, I submitted several pull requests against WooCommerce itself.

In total, my PRs would add about 2% code coverage to WooCommerce, all focused in uncovered (or under-covered) areas that our WooCommerce extension relies on. More tests for WooCommerce mean greater coverage within our plugin, while also helping other WooCommerce extensions at the same time.

Leave a Reply