Today I was working on a PHPUnit test at work for testing Elasticsearch. In this case, I wanted to make sure that the data that I was indexing was findable. Basically I wanted to run a search for a given query and check that the results were as expected. I wanted to run this search and evaluate process for a number of different search terms. The only difference with each test would be the query and the expected result. Instead of having to create a custom function or loop to do this, PHPUnit provides a handy @dataProvider
annotation that does just this. You define a function which will return an array of test cases, which then get run through your test function. You can give each test case a name. When you run the tests, PHPunit will report the results for each test case separately. It is very handy.
There is one tricky point though. In some of my tests, I am creating websites and indexing them on the fly. When I create a website or a post on that website’s blog, I am actually running production code with a real, local MySQL instance, and indexing into a real local Elasticsearch cluster. I am not simply mocking these calls. This is more of an integration test than a unit test. There are many configurations within MySQL and Elasticsearch which could cause failures or unexpected results. Testing this way allows me to have more confidence that my application is going to work the way I intend. When I write my test, I first create some test sites and content using the setUpBeforeClass
method. That is a static method, which means that if I want to store the id
of the website I created, I need to store that in a static variable, so I do that. However, this creates a minor issue with my data provider. If I try to access that static variable in my data provider, it will throw an error, because the code for the dataProvider function is compiled before the setUpBeforeClass
function has run to initialize those variables. There is a workaround though – closures! A closure is simply an anonymous function. Instead of returning some data from my dataProvider, I return a function which will get evaluated when the test is run, which is after setUpBeforeClass
has initialized the variables I need. Here is an example of a test I am running to test for searching for authors when a WordPress site is using the CoAuthorsPlus plugin. Notice how in order to use the closures, I must call them like a function using parentheses, like so: $main_author()
public function co_author_plus_sample_data() {
return array(
'regular_plus_guest_authors' => array(
'guest_authors' => array(
array(
'display_name' => 'mary smith',
'nicename' => 'cap-mary-smith',
),
array(
'display_name' => 'john smith',
'nicename' => 'cap-john-smith',
),
),
'regular_authors' => function () {
return array(
array(
'display_name' => self::$secondary_user->display_name,
'nicename' => self::$secondary_user->user_login,
),
array(
'display_name' => self::$tertiary_user->display_name,
'nicename' => self::$tertiary_user->user_nicename,
),
);
},
'main_author' => function () {
return false;
},
'expected_num_authors' => 4,
),
);
}
/**
* Tests that we can index co-author plus properly
* @dataProvider co_author_plus_sample_data
*/
public function test_co_authors_plus( $guest_authors, Closure $regular_authors, Closure $main_author, $expected_num_authors ) {
$guest_author_arr = wp_list_pluck( $guest_authors, 'nicename' );
$regular_author_arr = wp_list_pluck( $regular_authors(), 'nicename' );
$authors = array_merge( $guest_author_arr, $regular_author_arr );
$the_main_author = $main_author();
wp_set_post_terms( $post_id, $authors, 'author' );
// some more code here for actually indexing and querying data,
// which gets stored in $results
$this->assertFieldCount( $results, 0, 'author', $expected_num_authors, 1 );
When I run the test I get output like so:
phpunit --testdox --filter test_co_authors_plus suites/elasticsearch/Changes.php
✔ Co authors plus with data set "regular_plus_guest_authors"