Uses Faker and Brain Monkey to provide easy generation of fake WordPress objects and related functions.
Referring to Brain Monkey setup for WordPress we can extend it with Brain Faker capabilities.
class FakerTestCase extends \PHPUnit\Framework\TestCase
{
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
/**
* @var \Faker\Generator
*/
protected $faker;
/**
* @var \Brain\Faker\Providers
*/
protected $wpFaker;
protected function setUp(): void
{
parent::setUp();
\Brain\Monkey\setUp();
$this->faker = \Brain\faker();
$this->wpFaker = $this->faker->wp();
}
protected function tearDown(): void
{
\Brain\fakerReset();
\Brain\Monkey\tearDown();
parent::tearDown();
}
}
Besides of the usual setup for Brain Monkey, we have stored two properties in our test case instance:
$faker
that is an instance of Faker "extended" with Brain Faker
capabilities, that basically is a method ->wp()
$wpFaker
that an instance of \Brain\Faker\Providers
that is the entry point for all the methods that Brain Faker provides.Using the doc block on properties is recommended, because the IDE will be able to provide auto-complete for all the Faker and Brain Faker methods that can be called on them.
For the rest of the README this test setup is assumed, for example, there will be $this->wpFaker
used all over the places, but as long as an instance of Faker\Providers
is obtained via \Brain\faker()->wp()
and \Brain\fakerReset
is called at the end of each test, Brain Faker will work as expected.
It is also worth noting that, just like Brain Monkey, Brain Faker does not require PHPUnit, it is used here just for convenience.
All test case classes that will extend from the base test case class shown above can use $this->wpFaker
to generate:
WP_Post
instances, and mock related function like get_post
and get_post_field
WP_User
instances, and mock related function like get_userdata
, get_user_by
, user_can
, and moreWP_Term
instances, and mock related function like get_term
and get_term_by
WP_Comment
instancesWP_Site
instances, and mock related function like get_site
WP_Post_type
instances, and mock related function like get_post_type_object
and post_type_exists
WP_Taxonomy
instances, and mock related function like get_taxonomy
and taxonomy_exists
WP_Error
instancesThe instances generated by Brain Faker are "fake" not only in the meaning that don't come from database, but also because they are actually "mocked instances", that is they are obtained using Mockery.
class MyPostCase extends FakerTestCase
{
public function testPostsCanBeFaked()
{
$posts = $this->wpFaker->posts(10);
static::assertIsArray($posts);
static::assertCount(10, $posts);
foreach ($posts as $post) {
static::assertInstanceOf(\WP_Post::class, $post);
static::assertGreaterThanOrEqual(1, $post->ID);
static::assertIsString($post->post_title));
static::assertIsString($post->post_content));
static::assertIsArray($post->to_array());
}
}
}
$this->wpFaker->posts()
, the method we used to generate fake WP_Post
instances, accepts as first argument the number of posts to generate.
A second optional argument can be used to fix some properties one the generated posts, e. g. the following code:
$this->wpFaker->posts(10, ['type' => 'page', 'status' => 'publish']);
can be used to obtain 10 posts, all with post type "page" and post status "publish".
If not provided, all properties are generated randomly.
Besides $this->wpFaker->posts()
, Brain Faker also provides $this->wpFaker->post()
that returns a single instance of a mocked WP_Post
, and like posts()
, also accepts the optional parameter with the array of properties to assign to it.
It has been said already that first argument accepted by $this->wpFaker->posts()
is the number of objects we want to generate.
However, it is optional, and if not provided Brain Faker will generate a random number of objects (which could be zero).
It worth noting that calling $this->wpFaker->posts()
(with no arguments) is the same of doing $this->wpFaker->posts
, as usually happen in Faker.
Sometimes it is desirable to generate either a minimum or a maximum number of objects, and for this purpose Brain Faker provides two set of methods in the form:
$this->wpFaker->atLeast{$n}Posts()
, e. g. $this->wpFaker->atLeast6Posts()
$this->wpFaker->atMost{$n}Posts()
, e. g. $this->wpFaker->atMost20Posts()
Please note that, only for numbers between 1 and 5, it is possible to spell them in words, so for example, all the following are valid methods:
$this->wpFaker->atLeastOnePost()
$this->wpFaker->atMostTwoPost()
$this->wpFaker->atLeastThreePosts()
$this->wpFaker->atMostFourPosts()
$this->wpFaker->atLeastFivePosts()
Also note how the entity can be singular (e .g. Post
instead of Posts
), when number is One
(or 1
).
To set both the minimum and the maximum number of generated instances, Brain Faker provides a set of methods in the form:
$this->wpFaker->between{$min}And{$max}Posts()
for example:
$this->wpFaker->betweenOneAndThreePosts()
$this->wpFaker->between6And10Posts()
$this->wpFaker->between0AndFourPosts()
All these "dynamic-named" methods, accept the optional parameter with the array of properties to assign to the generated posts:
$posts = $this->wpFaker->atLeastTwoPosts(['type' => 'post']);
$pages = $this->wpFaker->betweenThreeAnd10Posts(['type' => 'page']);
All as been said above for posts is also true for other supported objects, in fact the list of Brain Faker methods includes:
for posts:
$this->wpFaker->posts()
$this->wpFaker->atLeast{$n}Posts()
$this->wpFaker->atMosts{$n}Posts()
$this->wpFaker->between{$min}and{$max}Posts()
$this->wpFaker->post()
for users:
$this->wpFaker->users()
$this->wpFaker->atLeast{$n}Users()
$this->wpFaker->atMosts{$n}Users()
$this->wpFaker->between{$min}and{$max}Users()
$this->wpFaker->user()
for terms:
$this->wpFaker->terms()
$this->wpFaker->atLeast{$n}terms()
$this->wpFaker->atMosts{$n}terms()
$this->wpFaker->between{$min}and{$max}Terms()
$this->wpFaker->term()
for comments:
$this->wpFaker->comments()
$this->wpFaker->atLeast{$n}comments()
$this->wpFaker->atMosts{$n}comments()
$this->wpFaker->between{$min}and{$max}Comments()
$this->wpFaker->comment()
for sites:
$this->wpFaker->sites()
$this->wpFaker->atLeast{$n}sites()
$this->wpFaker->atMosts{$n}sites()
$this->wpFaker->between{$min}and{$max}Sites()
$this->wpFaker->site()
for post types:
$this->wpFaker->postTypes()
$this->wpFaker->atLeast{$n}PostTypes()
$this->wpFaker->atMosts{$n}PostTypes()
$this->wpFaker->between{$min}and{$max}PostTypes()
$this->wpFaker->postType()
for taxonomy:
$this->wpFaker->taxonomies()
$this->wpFaker->atLeast{$n}Taxonomies()
$this->wpFaker->atMosts{$n}Taxonomies()
$this->wpFaker->between{$min}and{$max}Taxonomies()
$this->wpFaker->taxonomy()
for WP_Error
:
$this->wpFaker->errors()
$this->wpFaker->atLeast{$n}Errors()
$this->wpFaker->atMosts{$n}Errors()
$this->wpFaker->between{$min}and{$max}Errors()
$this->wpFaker->error()
For all the returned objects, Brain Faker sets all the objects public properties that real WordPress objects would have.
Thanks to Faker, all the properties will have random, but "realistic", value (emails will be emails, URLs will be URLs, passwords will be password, and so on).
Properties that do not exist as declared properties in WordPress objects, but that WordPress make available via magic method __get
, are available on fake objects as well, for example doing $this->wpFaker->user->user_login
would be perfectly valid, even if user_login
is a property that real WP_User
instances make available via magic method.
It has been said already that returned instances are mock objects obtained via Mockery.
It is important to say that not all methods are mocked on those instances.
For example, for posts, only WP_Post::to_array()
method is mocked, but other methods like WP_Post::filter()
are not mocked, but can surely be mocked "manually".
For example, the following is valid code:
$this->wpFaker->post
->shouldReceive('filter')
->once();
The methods that Brain Faker mocks vary, of course, from object to object.
On top of that, Brain Faker also mocks some WordPress "related functions".
Below there is the complete list of methods and functions that are mocked by Brain Faker for each kind of object it supports.
When creating fake WP_Post
objects, Brain Faker creates mocks with:
WP_Post
objects properties, including those that WordPress make available via __get
, such us page_template
;WP_Post::to_array()
method, that works exactly how WordPress version would work;get_post
function;get_post_field
function;For example:
class MyPostCase extends FakerTestCase
{
public function testGetPostCanBeFaked()
{
$this->wpFaker->post(['id' => 42, 'title' => 'The Answer']);
static::assertSame('The Answer', get_post(42)->post_title);
static::assertFalse(get_post(1));
static::assertIsString(get_post_field('post_content', 42));
}
}
When called with the ID of the post we faked (and we know it because was enforced via the provided array), get_post
returns the fake post we generated.
However, when called with another ID, it returns false
, just like WordPress would do for non-found post.
The mocked version of get_post
will be able to recognize all the posts that have been created inside a test (or in any case before Brain\fakerReset()
is called) and will resolve them correctly, returning false for post that have not been created.
Please note that the object return by get_post
is an object entirely comparable to the one faked, but is not the same instance, i. e. a strict equality comparison (===
) would fail.
The same is true for get_post_field
, and also all the other functions mocked by Brain Faker works in a similar way.
When creating fake WP_User
objects, Brain Faker creates mocks with:
WP_User
objects properties, including the many that WordPress make available via __get
, such us first_name
, last_name
, user_login
, and many others;WP_User::to_array()
, method; WP_User::exists()
, method; WP_User::get_site_id()
, method; WP_User::has_cap()
, method; get_user_data()
functionget_user_by()
functionuser_can()
functionMoreover, the faked WP_User
instances are the only fake objects created by Brain Faker that have an additional method that WordPress objects does not have, it is __monkeyMakeCurrent()
.
When called, __monkeyMakeCurrent()
will also mock (actually stub) the following functions:
get_current_user_id
that will return the ID of the instance on which __monkeyMakeCurrent()
is calledwp_get_current_user
that will return the instance on which __monkeyMakeCurrent()
is calledcurrent_user_can
that will call (the mocked) has_cap
method the instance on which __monkeyMakeCurrent()
is calledThis is extremely powerful because with one line of code it is possible to mock a lot of WordPress code without loading WordPress.
More so, considering that the fake WP_User
instances are mocked in a way that is extremely similar to real WordPress objects (this is true for all fake objects), for example the role and capabilities will be very "realistic".
For example, consider the following code:
class MyUserCase extends FakerTestCase
{
public function testCurrentUserCanBeFaked()
{
$this->wpFaker->user(['role' => 'editor'])->__monkeyMakeCurrent();
static::assertTrue(current_user_can('edit_others_pages'));
}
}
When creating fake WP_Term
objects, Brain Faker creates mocks with:
WP_Term
objects properties;WP_Term::to_array()
method;get_term
function, but only when called with the arguments that match the generated instances;get_term_by
function, but only when called with the arguments that match the generated instances;When creating fake WP_Comment
objects, Brain Faker creates mocks with:
WP_Comment
objects properties;WP_Term::to_array()
method;When creating fake WP_Site
objects, Brain Faker creates mocks with:
WP_Site
objects properties, including those that WordPress make available via __get
;WP_Site::to_array()
method;get_site()
function, but only when called with the IDs of the generated instances;When creating fake WP_Taxonomy
objects, Brain Faker creates mocks with:
WP_Taxonomy
objects properties;get_taxonomy()
functiontaxonomy_exists()
functionWhen creating fake WP_Post_Type
objects, Brain Faker creates mocks with:
WP_Post_Type
objects properties;get_post_type_object()
functionpost_type_exists()
functionWhen creating fake WP_Error
objects, Brain Faker creates mocks with:
WP_Error
objects properties, but with empty values;WP_Error
public methods;Fake WP_Error
instances are the only mocked objects created by Brain Faker whose properties are not "filled" randomly, but that are completely mocked, so they can be used as WordPress objects.
Imagine some WordPress code that looks like this:
function maybeAddError(\WP_Error $error): \WP_Error
{
if (is_error_there()) {
$error->add('code', 'A message', 'some data');
}
return $error;
}
Using Brain Faker (and Brain Monkey) we could test it like this:
class MyErrorCase extends FakerTestCase
{
/**
* Test that error have expected message and data
* when `is_error_there` returns true
*/
public function testMaybeAddErrorWhenErrorIsThere()
{
\Brain\Monkey\Functions\when('is_error_there')->justReturn(true);
$error = maybeAddError($this->wpFaker->error);
static::assertTrue($error->has_errors());
static::assertSame('A message', $error->get_error_message());
static::assertSame('some data', $error->get_error_data());
}
/**
* Test that error is empty when `is_error_there` returns false
*/
public function testMaybeAddErrorWhenErrorIsNotThere()
{
\Brain\Monkey\Functions\when('is_error_there')->justReturn(false);
$error = maybeAddError($this->wpFaker->error);
static::assertFalse($error->has_errors());
static::assertSame([], $error->get_error_messages());
}
}
Please note that even if all \WP_Error
methods are mocked, nothing prevents in tests to override already mocked methods (and this is true for all fake objects generated by Brain Faker).
For objects that support IDs (posts, users, terms, comments), unless those are given in the options array, an ID is generated randomly.
It is interesting to note that those randomly generated IDs are unique inside each tests (or at least as soon as Brain\fakerReset
is called).
For users besides ID also email and login are unique when generated randomly.
For example, doing $this->wpFaker->atLeast50Users()
Brain Faker will create at least 50 mocked user instances, each with a different ID, email and user login.
This feature is based on Faker unique()
modifier.
As Faker documentation states, the unique generator can be "reset". To do this when using Brain Faker it is possible to call $this->wpFaker->__resetUnique()
.
Note that calling __resetUnique()
manually (it is called automatically when Brain\fakerReset
is called) can conflict with mocked functions by Brain Monkey so it should be avoided unless one knows exactly what they is doing.
Faker supports localization for some of its providers.
To obtain a localized version of Faker, when using Brain Faker, it is possible to pass the desired locale to \Brain\faker()
function.
For example:
protected function setUp(): void
{
parent::setUp();
\Brain\Monkey\setUp();
$this->faker = \Brain\faker('fr_FR');
$this->wpFaker = $this->wpFaker->wp();
}
Of course, it is possible to have multiple instances of Faker (and Brain Faker), for different locales and also don't instantiate them on setUp
, but in the individual tests.
Even in that case, calling \Brain\fakerReset()
once at the end of the test will be fine.
All the methods that are mocked by Brain Faker for generated objects can be overridden easily.
For example:
class MyUserCase extends FakerTestCase
{
public function testUserCanBeMadeNotExistent()
{
$user = $this->wpFaker->user(['id' => 123]);
$user->shouldReceive('exist')->once()->andReturn(false);
static::assertFalse($user->exists());
}
}
In the example above the generated user has already a mocked exist
method, that would return true because the ID is bigger than zero.
However, we were able to override the method expectation and make it return false.
This is a great advantage of the fact that returned instances are actually mock objects.
Unfortunately, this approach can not be used for functions mocked by Brain Faker, due to a limitation of the underlying Brain Monkey.
For example:
class MyUserCase extends FakerTestCase
{
public function testBrainMonkeyFunctionsExpectIsIgnored()
{
$this->wpFaker->user(['id' => 123]);
Brain\Monkey\Functions\expect('get_userdata')
->with(123)
->andReturn(false);
static::assertSame(123, get_userdata(123)->ID);
}
}
The snippet above shows how the "manual" mocking of get_userdata
using Brain Monkey is ignored, and so get_userdata
keep returning the mocked user when called when its ID.
To overcome this issue, Brain Faker provides a method $this->wpFaker->__monkeyFunction()
that can be used to replace expectations for functions mocked by Brain Faker.
The test above could be rewritten in like this:
class MyUserCase extends FakerTestCase
{
public function testGetUserDataCanBeOverridden()
{
$this->wpFaker->user(['id' => 123]);
$this->wpFaker->__monkeyFunction('get_userdata')
->with(123)
->andReturn(false);
static::assertFalse(get_userdata(123));
}
}
Only thing to keep in mind is that __monkeyFunction
must be called after the fake objects has been created.
This seems to assume that, when using Brain Faker, one should remember all the functions that it mocks, and so to use $this->wpFaker->__monkeyFunction()
instead of Brain\Monkey\Functions\expect
to mock them.
That's not true.
$this->wpFaker->__monkeyFunction()
can always used as a replacement for Brain\Monkey\Functions\expect
, if fact, when used with a function that has not be mocked by Brain Faker, it will act as an alias for Brain\Monkey\Functions\expect
, so it might be a good idea to just use $this->wpFaker->__monkeyFunction()
when mocking functions in tests that make use of Brain Faker.
Brain Faker is released under MIT.
It requires PHP 7.1+
Can be installed via Composer, available on packagist.org as brain/faker
.
Via Composer it requires:
fzaninotto/Faker
(MIT)brain/monkey
(MIT)mockery/mockery
(BSD-3-Clause) - required by Brain Monkeyantecedent/patchwork
(MIT) - required by Brain Monkey