A WordPress functional testing module 06

Functional testing of authenticated WordPress operations.

Still a work in progress

The idlikethis WordPress plugin was born as a reference project for the wp-browser modules library built on top of Codeception.
The plugin is not complex in its functions and I’ve tried to touch any sensible WordPress API I could to show at least one test of each type.
For some time now I’ve worked on a module dedicated to functional testing in a separate branch and tried my hand, and the module capabilities, on a separate development branch of the “I’d like this” plugin repository.

From acceptance testing to functional testing

The plugin “functional” tests were using, previously, the PhpBrowser and WPDb modules.
While the second one has a place in functional tests the first one is really a JavaScript-less browser and as such tests made using it are really “acceptance” tests.
The first change to the plugin “functional” test suite happened in the tests/functional.suite.yml configuration file were the PhpBrowser module was replaced with the WordPress one:

class_name: FunctionalTester
modules:
    enabled:
        - WPDb:
            dsn: 'mysql:host=127.0.0.1;dbname=wp'
            user: root
            password: root
            dump: tests/_data/dump.sql
            populate: true
            cleanup: true
            url: 'http://wp.dev'
            tablePrefix: wp_
        - WordPress:
            depends: WPDb
            wpRootFolder: "/Users/Luca/Sites/wp"
            adminUsername: 'admin'
            adminPassword: 'admin'
        - \Helper\Functional

The WordPress module uses the database handling WPDb module to set-up and clean the database fixtures in between tests and that dependency is made explicit in the functional suite configuration above.
The tests have not changed much and the ShortcodeCest test case, as an example, has not changed its syntax at all:

namespace frontend;
class ShortcodeCest {
    /**
     * @test
     * it should render simple shortcode
     */
    public function it_should_render_simple_shortcode( \FunctionalTester $I ) {
        $content = 'Lorem ipsum [idlikethis]';
        $post_id = $I->havePostInDatabase( [ 'post_title' => 'Some title', 'post_content' => $content ] );
        $I->amOnPage( '/' );
        $text = "I'd like this";
        $I->seeElement( '.idlikethis-button[data-post-id="' . $post_id . '"][data-text="' . $text . '"] button' );
    }
    /**
     * @test
     * it should render extended shortcode
     */
    public function it_should_render_extended_shortcode( \FunctionalTester $I ) {
        $content = 'Lorem ipsum [idlikethis]Some idea of mine[/idlikethis]';
        $post_id = $I->havePostInDatabase( [ 'post_title' => 'Some title', 'post_content' => $content ] );
        $I->amOnPage( '/' );
        $text = "Some idea of mine";
        $I->seeElement( '.idlikethis-button[data-post-id="' . $post_id . '"][data-text="' . $text . '"] button' );
    }
}

Tests for more complex functions involving authenticated WordPress REST API calls have been modified to read and send nonce headers along with the request.
The code below verifies some post operations done from a post edit screen to “consolidate” comments generated by the plugin.

    /**
     * @test
     * it should not consolidate comments if current user cannot edit posts
     */
    public function it_should_not_consolidate_comments_if_current_user_cannot_edit_posts( \FunctionalTester $I ) {
        $I->haveUserInDatabase( 'someUser', 'subscriber', [ 'user_pass' => 'somePassword' ] );
        $I->loginAsAdmin();

        $post_id     = $I->havePostInDatabase();
        $comment_ids = $I->haveManyCommentsInDatabase( 3, $post_id, [ 'comment_type' => 'idlikethis' ] );

        $I->amEditingPostWithId( $post_id );

        // not sending headers hence the user will be set to a visitor and will not be able to edit posts

        $I->sendAjaxPostRequest( '/wp-json/idlikethis/v1/admin/consolidate-all', [
            'post_id' => $post_id
        ] );

        // not authorized
        $I->seeResponseCodeIs( 403 );
        foreach ( $comment_ids as $comment_id ) {
            $I->seeCommentInDatabase( [ 'comment_ID' => $comment_id, 'comment_post_ID' => $post_id ] );
        }
    }

    /**
     * @test
     * it should consolidate comments when post id is valid and user can edit posts
     */
    public function it_should_consolidate_comments_when_post_id_is_valid_and_user_can_edit_posts( \FunctionalTester $I ) {
        $post_id = $I->havePostInDatabase();

        $comment_ids = $I->haveManyCommentsInDatabase( 3, $post_id, [ 'comment_type' => 'idlikethis', 'comment_content' => '{{n}} - foo' ] );

        $I->loginAsAdmin();
        $I->amEditingPostWithId( $post_id );

        $wp_rest_nonce = $I->grabValueFrom( 'input[name="rest_nonce"]' );
        $I->haveHttpHeader( 'X-WP-Nonce', $wp_rest_nonce );

        $I->sendAjaxPostRequest( '/wp-json/idlikethis/v1/admin/consolidate-all', [
            'post_id' => $post_id
        ] );

        // operation completed successfully
        $I->seeResponseCodeIs( 200 );
        foreach ( $comment_ids as $comment_id ) {
            $I->dontSeeCommentInDatabase( [ 'comment_ID' => $comment_id, 'comment_post_ID' => $post_id ] );
        }
        $I->seePostMetaInDatabase( [ 'post_id' => $post_id, 'meta_key' => '_idlikethis_votes', 'meta_value' => serialize( [ 'foo' => 3 ] ) ] );
    }

Next

I will not withhold the release of the beta version of the module farther: after some cleanup and document updates it should be good enough to go.