Understanding WordPress PHP Unit Test Fixture Setup and Teardown

When I get started writing unit tests in WordPress, I continuously ran into what seemed to be strange behaviours by the WordPress Unit Testing framework. Strange when comparing to other PHP unit tests anyway.

It wasn’t until I read all the code of the bootstrap and install scripts, as well as the base WordPress unit test case, that I was able to make sense of it all.

So in this article, I’ll detail how WordPress Unit Testing framework handles fixtures. In addition, I’ll touch on a few things to watch out for when writing your own tests.

Database Fixtures

  1. The only data that persists during all test is that which can be simulated by using hooks in database retrieval functions.
  2. Options can be persisted by setting them in the $wp_tests_options global in the bootstrap.php file.
  3. Any fixtures created in wpSetUpBeforeClass() get deleted automatically by tearDownAfterClass() after all tests in a test class have run.
  4. Any fixtures created in a test get deleted automatically by tearDown after the test has run.
  5. Any changes to the database that still exist after having run all tests get reset during the next test run.

Hooks (Actions and Filters)

  1. Any actions or filters that need to persist during all tests need to be added in the bootstrap.php file.
  2. Any actions or filters that were added in wpSetUpBeforeClass() need to be reset in wpTearDownAfterClass().
  3. Any actions or filters that were added in setUp() get reset automatically during tearDown().

Fixture Setup During the Bootstrap and Install Scripts

  1. Each time that the unit tests are run, the database is reset to a fresh install.
  2. The default post is deleted afterwards.
  3. The permalink_structure option is left empty.
  4. The $wp_theme_directories global is set to data/themedir1, and the WP_DEFAULT_THEME constant is set to default.  The theme in data/themedir1/default is what gets used by default for tests. The functions.php file of this theme is not entirely empty, so beware of side-effects.
  5. The $_SERVER superglobal is filled with test data.
  6. The $phpmailer global is set to an instance of MockPHPMailer().
  7. The die handler is enabled and set to a function that outputs the die message.

Automatic Fixture Deletion

  1. setupBeforeClass():
    1. Sets PHP’s error reporting to display errors.
    2. Calls wpSetUpBeforeClass() if it exists, and passes the instance of  WP_UnitTest_Factory stored in a static property of the test case class as a method argument.
  2. tearDownAfterClass():
    1. Calls _delete_all_data() which deletes all data except the Uncategorized term and the default user.
    2. Calls the flush_cache() method, which resets the $wp_object_cache global.
    3. Calls wptearDownAfterClass() if it exists.
  3. setUp():
    1. Retrieves the files in the directory returned by wp_upload_dir() and stores them in the $ignore_files property. This property is used by the remove_added_uploads() method to reset the uploads folder in certain Core unit tests.
    2. Calls the _backup_hooks() method to save the current state of the $wp_actions, $wp_filter and $wp_current_filter globals.
    3. Calls the clean_up_global_scope() method, which resets the $_GET and $_POST superglobals, and calls the flush_cache() method.
    4. If the WP_RUN_CORE_TESTS constant is defined and true, post types, custom taxonomies, custom post statuses, the permalink structure, and the $_SERVER superglobal are reset between each test.
    5. Starts an SQL transaction, and filters the query hook, so that any CREATE TABLE or DROP TABLE statements are changed to CREATE TEMPORARY TABLE and DROP TEMPORARY TABLE.
    6. Calls the expectDeprecated() method, that remaps the Core _deprecated_*() functions to methods inside of the base test class.
    7. Changes the callback of the wp_die_handler filter to the get_wp_die_handler() method, which throws an exception of the type WPDieException whenever wp_die() is called.
  4. tearDown():
    1. Rolls back any changes made to the database.
    2. On multisite, calls restore_current_blog() if the tests called switch_to_blog().
    3. Erases the contents of the $wp and $wp_query globals with new instances of WP and WP_Query.
    4. Sets all globals related to The Loop to null:  $post, $id, $authordata, $currentday, $currentmonth, $page, $pages, $multipage, $more, and $numpages.
    5. Removes theme support for HTML5.
    6. Removes the query and wp_die_handler filter callbacks.
    7. Restores the state of the hooks previously saved by setUp().
    8. Sets the current user to the default user.

Things to Watch Out For

  1. If you are running the tests on the same install that you use for local development, WP_UnitTestCase::setUp() will scan the contents of your local uploads folder. This can make your tests really, really slow. To avoid this, use the upload_dir filter in the bootstrap file to point to a different uploads directory for the tests.
  2. Any plugins or themes that your tests need, need to be loaded individually.
  3. MU plugins will get used by your tests if you use your local development install to run the tests.
  4. If your tests rely on permalink settings, they need to be set in the bootstrap file, or in the tests themselves.

Level Up Your WordPress Business With One Email Per Week

Every Sunday, I send out tips, strategies, and case studies designed to help agencies and freelancers succeed with modern WordPress.

My goal is to go off the beaten path, and focus on sharing lessons learned from what I know best: building websites for clients.

100% free and 100% useful.

Feel free to check out the latest editions.