jjgrainger / PostTypes

Simple WordPress custom post types.
https://posttypes.jjgrainger.co.uk/
MIT License
373 stars 47 forks source link

Add unit tests #9

Closed jjgrainger closed 6 years ago

jjgrainger commented 7 years ago

I've been wanting to add unit tests for a while but wasn't sure how. After some research, I've stumbled across a few interesting articles and tools that have shed some light on how to do this.

It has become clear that there are a couple of issues with how the class has been made so far.

Calling hooks in the class constructor

Though hooks aren't specifically called in the __construct() method, there is an initialize() method that is. This method adds all of the hooks the class registers to register post types, taxonomies and columns. Having initialize() called in the constructor makes it difficult to test PostTypes in isolation of WordPress.

To fix this, I'm looking at renaming the initialize() method to register(). This will allow PostTypes to be tested in isolation of WordPress, as the method that hooks into WordPress has to be called explicitly. However, Doing this will mean the developer has to call register() themselves in order to register their post type.

$books = new PostTypes('book');

$books->taxonomy('genre');

// ... Additional post type work here

// Register the post type to WordPress
$books->register();

Concrete dependencies and dependency injection

PostTypes has 3 Classes, PostType, Columns and Taxonomy. PostType is the main class that handles everything from registering post types, taxonomies and modifying the admin columns.

Both Columns and Taxonomy are helper classes. They only really store information in a structured way to be used by PostType later. These classes are explicitly created in the __construct method.

Though this won't necessarily cause issues, it's still not good practice. So may need to look into alternatives to instantiating these additional classes.

Use of globals

There are some WordPress globals used inside some methods. Most seem unnecessary so should be simple enough to remove.

Positives

Despite the issues raised, I feel quite positive. The research has helped to clarify some principles for the class and how to approach unit testing it.

I'll will most likely edit/comment on this issue as I work on it

pixeltherapy commented 7 years ago

I've been looking to do the same for some of my own WP themes but when I sit down to refactor for dependency injection my eyes start glazing over at the task ahead. I'm where you are in terms of seeing where you want to be and how to get there, minus the optimism.

I'm borrowing your custom post class for a minute to rule out bugs in my own class but I like your tidy approach to adding admin edit columns. I look forward to seeing how it develops.

As an aside, you might consider using "require-dev" for unit test and similar tooling to reduce bloat in production use.

jjgrainger commented 7 years ago

Following on from issue #11 the Taxonomy class could become a standalone thing, available to developers to register taxonomies.

Taxonomies could be created as objects then be passed to the post type to register it, or could still use the taxonomy slug.

// register a post type
$books = new PostType('book');

// register a taxonomy
$genre = new Taxonomy('genre');

// register the taxonomy to the books post type
// using the taxonomy object
$books->taxonomy($genre);
// or with the taxonomy slug
$books->taxonomy('genre');

// register the post type
$books->register();

Using the taxonomy class to create objects can then open up additional methods for manipulating the taxonomy.

$genre = new Taxonomy('genre');

// modify the taxonomy admin columns
$genre->columns()->add([
    'populatrity_score' => __('Popularity')
]);

Then there is how the taxonomy is registered, can it register itself to a post type, or does that have to be done through a PostType object? Or both?

// register taxonomy to post type
$genre->postType('post');
// alternativly, if a books PostType object is available
$books->taxonomy('genre');

Then there is whether we need to provide it with a register() method too.

$genre->register();

Will explore this further...

pixeltherapy commented 7 years ago

I like how tidy all this looks. I'm going to give it a whirl in my dev theme and see what issues arise.

jjgrainger commented 7 years ago

Now the Taxonomy class is an independent thing from PostType the last dependency to fix is the Columns. Something I've discovered recently is that you can type hint a parameter but also default it to null.

In light of this, I'm thinking the columns() method in the PostType (and soon to be in Taxonomy) can accept a ColumnManager dependency. This can default to null, which then will instantiate the default Column class and assign it.

The method will look something like:

class PostType
{

    //... additional methods and properties

    public var $columns;

    public function columns(ColumnManager $columns = null)
    {
        // if the column manager is already set, return it
        if (isset($this->columns)) {
            return $this->columns;
        }

        // if no custom ColumnManager is passed
        if (is_null($columns)) {
            // instatiate one using PostTypes default
            $this->columns = new PostTypes\ColumnManager;
        } else {
            // otherwise assign the custom ColumnManager
            $this->columns = $columns;
        }

        return $this->columns;
    }
}

This way when creating PostTypes and Taxonomy classes:

// create post type
$books = new PostType('book');

// default to PostType\ColumnManager
$books->columns()->add([...]);

// using a custom ColumnManager
$books->columns(new MyCustomColumnManager)->add([...]);

// register the post type to WordPress
$books->register();