Most WordPress functions interact with data objects like posts, users, or terms. This creates a problem for developers, as every test starts with an empty WordPress installation. The WordPress Unit Test Factory helps to solve this issue.

It provides a set offers a set of helper classes to create these database objects, called database fixtures in a unit testing scenario.

The test framework offers factories for creating:

  • Attachments
  • Comments
  • Posts
  • Categories, Tags, and Terms
  • Users

It also has multisite specific factories for:

  • Networks
  • Blogs

In this article, we’ll see about how you can interact with these factories, and what issues to watch out for.

How to access factories inside of tests

The entry point for interacting with the factories is the WP_UnitTest_Factory class.

This class is a singleton, meaning that there is only one instance of it available. You can retrieve this singleton instance in a test using the static WP_UnitTestCase::factory() method.

This main factory class instantiates the individual factory classes for each content object, and stores them inside of member variables:

// Code snippet from WP_UnitTest_Factory: function __construct() { $this->post = new WP_UnitTest_Factory_For_Post( $this ); $this->attachment = new WP_UnitTest_Factory_For_Attachment( $this ); $this->comment = new WP_UnitTest_Factory_For_Comment( $this ); $this->user = new WP_UnitTest_Factory_For_User( $this ); $this->term = new WP_UnitTest_Factory_For_Term( $this ); $this->category = new WP_UnitTest_Factory_For_Term( $this, 'category' ); $this->tag = new WP_UnitTest_Factory_For_Term( $this, 'post_tag' ); $this->bookmark = new WP_UnitTest_Factory_For_Bookmark( $this ); if ( is_multisite() ) { $this->blog = new WP_UnitTest_Factory_For_Blog( $this ); $this->network = new WP_UnitTest_Factory_For_Network( $this ); } }

To access one of these factory instances inside of a test, you would access the corresponding property of the WP_UnitTest_Factory class:

class Test_Sample_Post_Factory_Usage extends WP_UnitTestCase { public function test_creates_a_valid_post() { $post_id = self::factory()->post->create(); $this->assertInstanceOf( WP_Post::class, get_post( $post_id ); } }

To explain the code snippet above:

  • All WordPress unit tests are sub-classes of WP_UnitTestCase. So to access the factory() method, you need to use self::factory().
  • Once you have called the factory() method, you get an instance of the WP_UnitTest_Factory, and can access its properties and methods.
  • To access the post factory, you access the post property of the unit test factory: self::factory()->post.
  • To create a post in the database, you call the create() method of the WP_UnitTest_Factory_For_Post class instance: self::factory()->post->create().
  • The WP_UnitTest_Factory_For_Post::create() method creates a post in the database, and returns its post id.

create() is just one of the methods available as part of the unit test factory. We will look at these in the next section.

How to create database fixtures using the factory methods

All unit test factories are base classes of WP_UnitTest_Factory_For_Thing, and therefore all have a common set of methods.

Shared Factory Interface

All factories inherit from the same base class, WP_UnitTest_Factory_For_Thing. It defines a common interface for creating, updating, and retrieving all types of content. We’ll review these methods, and what they do.

create_object()

create_object( array $args ) : mixed

The Thing Factory contains an abstract create_object() method, that the individual content factory classes need to implement. This method creates a single object.

For the Post Factory, this methods wraps wp_insert_post(). Code snippet from WP_UnitTest_Factory_For_Post:

function create_object( $args ) { return wp_insert_post( $args ); }

You’ll rarely use these methods directly, as there is no advantage for using them over the lower level insertion APIs.

create()

create( array $args = [], array $generation_definitions = null ) : int

This method is the main way for creating objects, and is inherited by all the individual content object factories. The difference between this method and create_object() is that it creates default values that are passed on to the object creation function. This ensures that there’s no conflicts between the objects created via the factory. As an example, the Post Factory sets the following defaults:

'post_status' => 'publish', 'post_title' => new WP_UnitTest_Generator_Sequence( 'Post title %s' ), 'post_content' => new WP_UnitTest_Generator_Sequence( 'Post content %s' ), 'post_excerpt' => new WP_UnitTest_Generator_Sequence( 'Post excerpt %s' ), 'post_type' => 'post',

create_and_get()

create_and_get( array $args = [], array $generation_definitions = null ) : object

This method differs from create() in that it returns an instance corresponding object class of the created object.

As an example, WP_UnitTest_Factory_For_Post::create_and_get() returns a WP_Post instance.

create_many()

create_many( int $count, array $args = [], array $generation_definitions = null ) : []

This method creates a set of objects, and returns an array containing their ids.

update_object()

update_object( mixed $object, array $fields ) : mixed

This abstract method is used for updating already created objects.

In case of the Post Factory, this method wraps wp_update_post().

get_object_by_id()

`get_object_by_id( int $object_id ) : object

The third and last abstract method is get_object_by_id(). It returns an instance of the object class for the requested id.

For the Post Factory, this method is a wrapper around get_post().

What are the default values?

The Default Values chapter contains more details about the default value creation.

Attachments

Posts

FieldTypeDefault Value
post_contentstringPost content {number}
post_excerptstringPost excerpt {number}
post_statusstringpublish
post_titlestringPost title {number}
post_typestringpost

Categories, Tags, and Terms

FieldTypeDefault Value
descriptionstringTerm description {number}
namestringTerm {number}
taxonomystringpost_tag

Comments

FieldTypeDefault Value
comment_approvedinteger1
comment_authorstringCommenter {number}
comment_author_urlstringhttp://example.com/{number}/
comment_contentstringThis is a comment

Users

FieldTypeDefault Value
user_loginstringUser {number}
user_passstringpassword
user_emailstringuser_{number}@example.org

Blogs

FieldTypeDefault Value
domainstringValue of $GLOBALS['current_site->domain'] variable, which should be the value of the WP_TESTS_DOMAIN constant
pathstringtestpath{Number}, prepended with the network path, which should be /
titlestringSite {Number}
site_idstringValue of $GLOBALS['current_site-> id'] variable.

Networks

FieldTypeDefault Value
domainstringValue of the WP_TESTS_DOMAIN constant
titlestringNetwork {Number}
pathstring/testpath{Number}/
network_idinteger{Number}
subdomain_installboolfalse

Known Issues

Posts

The post_modified and post_modified_gmt fields cannot be set when creating or updating posts. If you need to set these values explicitly, you need to use WP_UnitTestCase::update_post_modified().

Terms

  • Do not use update_object(), as it does not reliably work with term ids.
  • Do not use get_object_by_id(), as it does not reliably work with term ids.

Blogs

update_object() is an empty method, as blogs can’t be updated.

Networks

  • Subdomain installs are not supported by the Testing Framework, see Multisite in WPUT config
  • update_object() is an empty method, as networks can’t be updated.