The WP_UnitTestCase class has a factory() method that gives access to the WP_UnitTest_Factory factory classes.
And it’s these classes that you use to create data objects (called fixtures) like posts, users, or terms in your tests.
So here is an example that creates a post:
<?php
// Creates a post in the database and returns the post id.
$post = self::factory()->post->create();
But there’s more–you can use the WP_UnitTest_Factory class to create:
- Attachments
- Comments
- Posts
- Categories, Tags, and Terms
- Users
Or if you want to test code written for a multisite, you can also create networks and blogs.
Unfortunately the documentation for these factories is not great. This article fixes that, as it is a complete guide to the factories in the WordPress unit test suite.
Table of contents
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 thefactory()method, you need to useself::factory(). - Once you have called the
factory()method, you get an instance of theWP_UnitTest_Factory, and can access its properties and methods. - To access the
postfactory, you access thepostproperty of the unit test factory:self::factory()->post. - To create a post in the database, you call the
create()method of theWP_UnitTest_Factory_For_Postclass 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
| Field | Type | Default Value |
|---|---|---|
post_content | string | Post content {number} |
post_excerpt | string | Post excerpt {number} |
post_status | string | publish |
post_title | string | Post title {number} |
post_type | string | post |
Categories, Tags, and Terms
| Field | Type | Default Value |
|---|---|---|
description | string | Term description {number} |
name | string | Term {number} |
taxonomy | string | post_tag |
Comments
| Field | Type | Default Value |
|---|---|---|
comment_approved | integer | 1 |
comment_author | string | Commenter {number} |
comment_author_url | string | http://example.com/{number}/ |
comment_content | string | This is a comment |
Users
| Field | Type | Default Value |
|---|---|---|
user_login | string | User {number} |
user_pass | string | password |
user_email | string | user_{number}@example.org |
Blogs
| Field | Type | Default Value |
|---|---|---|
domain | string | Value of $GLOBALS['current_site->domain'] variable, which should be the value of the WP_TESTS_DOMAIN constant |
path | string | testpath{Number}, prepended with the network path, which should be / |
title | string | Site {Number} |
site_id | string | Value of $GLOBALS['current_site-> id'] variable. |
Networks
| Field | Type | Default Value |
|---|---|---|
domain | string | Value of the WP_TESTS_DOMAIN constant |
title | string | Network {Number} |
path | string | /testpath{Number}/ |
network_id | integer | {Number} |
subdomain_install | bool | false |
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.