For those who are experienced with the Settings API, you may wish to skip down to the core problem.

How To Unit Test WordPress Settings API Validators

I’m currently building a web application where WordPress is serving as the core framework. I’ve discussed this before and Matt covered this in State of the Word 2012 as something that will become more common as WordPress grows in popularity.

So WordPress doesn’t necessarily have an MVC or MVVM or whatever design pattern, but it offers its own method for how data models, business logic, and other necessary components should be created.

I’m using the Settings API to create a model that represents a user in the application. Essentially, it will wrap the core WordPress user model, but I have to introduce some additional attributes and ultimately create relationships with other models that WordPress doesn’t natively support.

Anyway, I’m writing unit tests for everything that’s going into the application and I hit an interesting point when it came to unit testing the validation functions.

A Note About Validation Functions

If you’re not familiar with the Settings API or with validation functions, check out the codex article. But here’s the short of it:

  • Settings are the options that the user tweaks in the dashboard
  • The validation function is responsible for making sure the user doesn’t input any incorrect values
  • If validation passes, the settings are saved; otherwise, an error message should be returned

Clear enough, right?

Unit Testing My Validators

For all intents and purposes, my validation function tests to see if all of the fields aren’t empty and are well-formed:

Unit Test WordPress Settings API Validators

For the fields and validation that you see above, this is being covered by some 26 assertions.

Part of the assertions I’ve written are making sure that the proper error message is being returned when a given input fails.

But here’s where I hit a snag: I wanted to clear our the settings error messages before each test ran in order to have an empty collection of error messages. Instead, WordPress continues to collect all of the error messages into it’s collection without clearing them.

For reference, the validator function is using add_settings_errors to introduce the error messages and I’m using get_settings_error to read said messages as seen here:

function testFirstNameFailure() {

	ai_individual_options_validate(
		array(
			'first_name'	=>	'Tom',
			'last_name'		=>	'',
			'email_address'	=>	'',
			'zip_code'		=>	''
		)
	);

	// First, we make sure only three error messages are returned
	$this->assertSame( 3, count( get_settings_errors( 'individual_options' ) ) );

	// Next, we make sure that the error message for the first name isn't returned
	foreach( get_settings_errors( 'individual_options' ) as $error ) {
		$this->assertNotSame( 'You must provide a first name.', $error['message'] );
	} // end foreach

} // end testFirstNameFailure

Initially, there are six possible errors messages – one for each empty field, then two for whether or not the email address and zip code are well-formed.

The way WordPress collects its errors, the first run may have four error messages, but the next iteration may have eight. And that’s incorrect – at any given time, it should always have only the total number of possible messages.

Adjusting The Teardown Function

Since teardown runs after each test, I decided to clear out the settings errors collection in this function.

Unfortunately, I could find no API method for emptying the collection nor could I find any other way to do. So I looked into the WordPress source, noticed the global variable that was being used to house the errors, and opted to manually re-initialize it each time teardown is run:

function tearDown() {

	parent::tearDown();

	global $wp_settings_errors;
	$wp_settings_errors = array();

} // end tearDown

This ended up providing a viable solution, but I’ve not idea if this is the best way to do it.

I really have no idea if this is the best practice or not, but if you guys have advice to offer, I’m all ears.