Content Restriction Plugin 13

Refactoring tests for data providers.

What posts

Resuming from where I had left the other side of this code

// each 2' apply the default restrictions to some unrestricted posts
tad_reschedule( 'trc/core/unrestricted_posts/check' )
    ->each( 120 )
    ->until( array( trc_Core_PostDefaults::instance(), 'has_unrestricted_posts' ) );

is a function of sort taking charge of applying a default restriction to posts that have none when the trc/core/unrestricted_posts/check action fires.
Taking into account the possibility of thousands of unrestricted posts proceeding to the application of a default restriction, one or more taxonomy terms, to each in a single run would clog the server up and/or cause time-out issues.
To cope with that I will add a parameter to feed the function that will trigger on that action: some unrestricted post IDs.
The amount of "some" to be decided in the trc_Core_PostDefaults class.

// each 2' apply the default restrictions to some unrestricted posts
$post_defaults = trc_Core_PostDefaults::instance();
tad_reschedule( 'trc/core/unrestricted_posts/check' )
    ->each( 120 )
    ->until( array( $post_defaults, 'has_unrestricted_posts' ) )
    ->with_args( $post_defaults->get_unrestricted_posts() );

New method, new tests

This creates the necessity for the development of the trc_Core_PostDefaults::get_unrestricted_posts() method.
The method responsibilities are simple and will allow me to write the tests to deem it working:

  • it should return an array of existing post IDs by restricting taxonomy
  • it should take an optional parameter to return all or up to a finite amount of existing post IDs
  • it should return an empty array if there are no posts that require a default restriction
  • it should take an optional parameter, post_type, to return post IDs of that type only

This outlines set it's time to write some tests.

Data providing a test

The first test I've written, covering the first bullet point, is the one below

/**
 * @test
 * it should return an array of existing unrestricted post IDs
 */
public function it_should_return_an_array_of_existing_unrestricted_post_i_ds() {
    $taxonomies = [
        'tax_1' => [ 'term_11' ],
        'tax_2' => [ 'term_21' ]
    ];

    foreach ( $taxonomies as $tax => $terms ) {
        register_taxonomy( $tax, [ ] );
        register_taxonomy_for_object_type( $tax, 'post' );
        foreach ( $terms as $t ) {
            wp_insert_term( $t, $tax, [ 'slug' => $t ] );
        }
        $user_slug_provider = Test::replace( 'trc_Public_UserSlugProviderInterface' )
                                  ->method( 'get_default_post_terms', $terms )
                                  ->get();
        $this->sut->set_user_slug_provider_for( $tax, $user_slug_provider );
    }

    $this->factory->post->create_many( 5, [ 'post_type' => 'post' ] );

    $out = $this->sut->get_unrestricted_posts();

    Test::assertCount( 2, $out );
    Test::assertArrayHasKey( 'tax_1', $out );
    Test::assertArrayHasKey( 'tax_2', $out );
    Test::assertCount( 5, $out['tax_1'] );
    Test::assertCount( 5, $out['tax_2'] );
}

reading the code reveals the test is covering one scenario:

There are two taxonomies restricting the post post type, tax_1 and tax_2, each one providing one default restriction term. Inserting five posts in the database will yield an array that stores, using the two taxonomy names as keys, the five post IDs.

While this works I do not want to write any more code than necessary to test more cases like:

  • 1 taxonomy and one default term
  • 1 taxonomy and no default term
  • 2 taxonomies one providing a default term and one not providing a default term

and so on. I'm very glad PHPUnit comes with data providers.

Refactoring the test to use data providers

The first step I take is to move any variable in play into the test method up refactoring and extracting code

/**
 * @test
 * it should return an array of existing unrestricted post IDs
 */
public function it_should_return_an_array_of_existing_unrestricted_post_i_ds() {
    $taxonomies                 = [
        'tax_1' => [ 'term_11' ],
        'tax_2' => [ 'term_21' ]
    ];
    $taxonomies_w_default_terms = array_filter( $taxonomies, function ( $terms ) {
        return ! empty( $terms );
    } );
    $post_type                  = 'post';
    $post_count                 = 5;

    foreach ( $taxonomies as $tax => $terms ) {
        register_taxonomy( $tax, [ ] );
        register_taxonomy_for_object_type( $tax, $post_type );
        foreach ( $terms as $t ) {
            wp_insert_term( $t, $tax, [ 'slug' => $t ] );
        }
        $user_slug_provider = Test::replace( 'trc_Public_UserSlugProviderInterface' )
                                  ->method( 'get_default_post_terms', $terms )
                                  ->get();
        $this->sut->set_user_slug_provider_for( $tax, $user_slug_provider );
    }

    $this->factory->post->create_many( $post_count, [ 'post_type' => $post_type ] );

    $out = $this->sut->get_unrestricted_posts();

    if ( $post_count ) {
        Test::assertCount( count( $taxonomies_w_default_terms ), $out );
        foreach ( array_keys( $taxonomies_w_default_terms ) as $tax_name ) {
            Test::assertArrayHasKey( $tax_name, $out );
            Test::assertCount( $post_count, $out[ $tax_name ] );
        }
    } else {
        Test::assertEmpty( $out );
    }
}

This allows me to know I will have to provide my test method with:

  • an array of taxonomies and their default terms
  • a post type
  • a post count

Tests methods should be as dumb as possible to avoid additional layers of complication but this does not mean absolutely dumb.
To make sure the test method refactor works I will feed the method a data provider that is providing only that same scenario

public function unrestrictedPostCombos() {
    return [
        [ [ 'tax_1' => [ 'term_11' ], 'tax_2' => [ 'term_21' ] ], 'post', 5 ]
    ];
}

/**
 * @test
 * it should return an array of existing unrestricted post IDs
 * @dataProvider unrestrictedPostCombos
 */
public function it_should_return_an_array_of_existing_unrestricted_post_i_ds( $taxonomies, $post_type, $post_count ) {
    $taxonomies_w_default_terms = array_filter( $taxonomies, function ( $terms ) {
        return ! empty( $terms );
    } );

    foreach ( $taxonomies as $tax => $terms ) {
        register_taxonomy( $tax, [ ] );
        register_taxonomy_for_object_type( $tax, $post_type );
        foreach ( $terms as $t ) {
            wp_insert_term( $t, $tax, [ 'slug' => $t ] );
        }
        $user_slug_provider = Test::replace( 'trc_Public_UserSlugProviderInterface' )
                                  ->method( 'get_default_post_terms', $terms )
                                  ->get();
        $this->sut->set_user_slug_provider_for( $tax, $user_slug_provider );
    }

    $this->factory->post->create_many( $post_count, [ 'post_type' => $post_type ] );

    $out = $this->sut->get_unrestricted_posts();

    if ( $post_count ) {
        Test::assertCount( count( $taxonomies_w_default_terms ), $out );
        foreach ( array_keys( $taxonomies_w_default_terms ) as $tax_name ) {
            Test::assertArrayHasKey( $tax_name, $out );
            Test::assertCount( $post_count, $out[ $tax_name ] );
        }
    } else {
        Test::assertEmpty( $out );
    }
}

and run it to make sure test is still working as intended; sure of that I'm leaving the test method code alone and am only adding scenarios to the data provider method

public function unrestrictedPostCombos() {
    return [
        [ [  ], 'post', 5 ],
        [ [ 'tax_1' => [ ], 'tax_2' => [  ] ], 'post', 5 ],
        [ [ 'tax_1' => ['term_11' ], 'tax_2' => [  ] ], 'post', 5 ],
        [ [ 'tax_1' => ['term_11','term_12' ], 'tax_2' => ['term_21'  ] ], 'post', 5 ],
        [ [ 'tax_1' => ['term_11','term_12' ], 'tax_2' => ['term_21','term_22'  ] ], 'post', 5 ],
        [ [ 'tax_1' => ['term_11','term_12' ], 'tax_2' => ['term_21','term_23'  ],'tax_3' => ['term_31','term_33'  ] ], 'post', 5 ],
        [ [ 'tax_1' => [], 'tax_2' => [],'tax_3' => ['term_31','term_33'  ] ], 'post', 5 ],
        [ [ 'tax_1' => [ 'term_11' ], 'tax_2' => [ 'term_21' ] ], 'post', 5 ],
        [ [  ], 'post', 0 ],
        [ [ 'tax_1' => [ ], 'tax_2' => [  ] ], 'post', 0 ],
        [ [ 'tax_1' => ['term_11' ], 'tax_2' => [  ] ], 'post', 0 ],
        [ [ 'tax_1' => ['term_11','term_12' ], 'tax_2' => ['term_21'  ] ], 'post', 0 ],
        [ [ 'tax_1' => ['term_11','term_12' ], 'tax_2' => ['term_21','term_22'  ] ], 'post', 0 ],
        [ [ 'tax_1' => ['term_11','term_12' ], 'tax_2' => ['term_21','term_23'  ],'tax_3' => ['term_31','term_33'  ] ], 'post', 0 ],
        [ [ 'tax_1' => [], 'tax_2' => [],'tax_3' => ['term_31','term_33'  ] ], 'post', 0 ],
        [ [ 'tax_1' => [ 'term_11' ], 'tax_2' => [ 'term_21' ] ], 'post', 0 ],
    ];
}

and watch the result. [Data-provider fed tests](http://theaveragedev.local/wordpress/wp-content/uploads/2015/08/2015-08-09-at-17.23.png)

Code

Current code to this post is on GitHub.