Skip to content

Latest commit

 

History

History
572 lines (378 loc) · 21.1 KB

README.md

File metadata and controls

572 lines (378 loc) · 21.1 KB

Brain Faker

license travis-ci status codecov.io PHP version requirement

Uses Faker and Brain Monkey to provide easy generation of fake WordPress objects and related functions.


Tests setup

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.

What can be faked

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 more
  • WP_Term instances, and mock related function like get_term and get_term_by
  • WP_Comment instances
  • WP_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 instances

The 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.

Quick example

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']);

Not only posts

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()

About returned instances

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.

What is mocked

What is mocked for Posts

When creating fake WP_Post objects, Brain Faker creates mocks with:

  • all the 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.

What is mocked for Users

When creating fake WP_User objects, Brain Faker creates mocks with:

  • all the 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() function
  • get_user_by() function
  • user_can() function

Moreover, 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 called
  • wp_get_current_user that will return the instance on which __monkeyMakeCurrent() is called
  • current_user_can that will call (the mocked) has_cap method the instance on which __monkeyMakeCurrent() is called

This 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'));
    }
}

What is mocked for Terms

When creating fake WP_Term objects, Brain Faker creates mocks with:

  • all the 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;

What is mocked for Comments

When creating fake WP_Comment objects, Brain Faker creates mocks with:

  • all the WP_Comment objects properties;
  • WP_Term::to_array() method;

What is mocked for Sites

When creating fake WP_Site objects, Brain Faker creates mocks with:

  • all the 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;

What is mocked for Taxonomies

When creating fake WP_Taxonomy objects, Brain Faker creates mocks with:

  • all the WP_Taxonomy objects properties;
  • get_taxonomy() function
  • taxonomy_exists() function

What is mocked for Post Types

When creating fake WP_Post_Type objects, Brain Faker creates mocks with:

  • all the WP_Post_Type objects properties;
  • get_post_type_object() function
  • post_type_exists() function

What is mocked for Errors

When creating fake WP_Error objects, Brain Faker creates mocks with:

  • all the WP_Error objects properties, but with empty values;
  • all the 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).

Advanced topics

On IDs uniqueness

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.

Localized Faker

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.

Overriding Brain Faker methods and functions

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.

Installation, Requirements, License

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 Monkey
  • antecedent/patchwork (MIT) - required by Brain Monkey