1

I am trying to learn TDD and am struggling with creating factories for custom objects. For instance, if I have a custom user type and all users of that type must have a specific capability, it's cumbersome to use the WP_UnitTest factory to create a user and then add the capability manually in each test before using the object. Because I need to use these objects in a variety of test files, it would be redundant to have a factory function in each test file/class, and it's a pain to manually implement all four variations of the factory methods (create, create_and_get, create_many, and create_and_get_many).

What is the best way to create a factory for unit test objects like this?

1

If I undertsand correctly, what you are searching for is a good library for creating stubs and mocks.

If you use PHPUnit, it has a features for that. See https://phpunit.de/manual/current/en/test-doubles.html#test-doubles

For example, let's assume you have a class like this:

namespace MyApp;

class MyCustomUser {

   public function __construct(\WP_User $user) {
     $this->user = $user;
   }

   public function hasCapability($capability) {
      return $this->user->can($capability);
   }
}

You could create a trait that holds an user factory and make use of mocking feature of PHPUnit:

namespace MyApp\Tests;

trait CreateUserTrait {

   protected function createUser(array $capabilitites) {

      $stub = $this->createMock(MyCustomUser::class);

      $has_capability = function($capability) use ($capabilitites) {
         return in_array($capability, $capabilitites, true);
      };

      $stub->method('hasCapability')->will($this->returnCallback($has_capability));
   }
}

At this point in your test classes you can use that trait and make use of the factory:

namespace MyApp\Tests;

use PHPUnit\Framework\TestCase;

class UserMockTest extends TestCase
{
    use CreateUserTrait;

    public function testUserFactoryWorks()
    {
        $userMock = $this->createUser(['create_post']);

        $this->assertTrue($userMock->hasCapability('create_post'));
    }
}

Of course, mocks and stubs are useful whan you need to test other objects that make use of mocked object. You won't mock the SUT.

A nice side effect of using the mock is we did not used any WordPress function or class in the test (even if the original user object maks use of WordPress WP_User object), so the test could be ran without loading WordPress, making it a real unit test: if you load WordPress it becomes an integration test, not an unit test.

Many people find the mocking syntax of PHP a bit hard to digest. If you are one of them, you might want to have a look at Mockery, which has a simpler API.

Depending on your needs, Faker might also be of great help.


Note: code above requires PHP 5.5 and the PHPUnit version assumed is version 5.* which requires PHP 5.6+

| improve this answer | |
  • This isn't really what I had in mind. I'm using the WP_UnitTest testing suite, and it already has stubs/mocks. For instance, I can create a new user test object with $this->factory->user->create(). What I'm trying to do is extend that functionality, but I can't parse the code for WP_UnitTest_Factory_For_Thing and WP_UnitTest_Factory_For_User (_Post, etc.) well enough to determine what I need to do. – philosophyguy Jan 12 '17 at 20:50
  • 1) WP_UnitTest testing suite isn't good for TDD. To use it you need to load WP environment and that is wrong conceptually and pratically because loading WP, tests are much much slower, which is relevant during development. See this answer wordpress.stackexchange.com/questions/164121/… for more details. – gmazzap Jan 13 '17 at 10:47
  • 2) With stubs and mocks you can make an object behave how do you like in a very simple way. The mine was just an example, but create a mock that "mimic" any feature of your objects is just few lines of code, that you can write once and use in all tests you need, as shown in my example @philosophyguy – gmazzap Jan 13 '17 at 10:47
1

I've written a tutorial on creating your own PHPUnit factories. In this case though, you really just need to extend the existing user factory, WP_UnitTest_Factory_For_User. If you take a look at the source of that class, you will see that you don't need to implement a bunch of different methods, just one: create_object(). That will be used by the other methods you mention.

Here is what the method looks like in the user factory:

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

Pretty simple, huh?

So all you need to do is modify it to do is create a child class of that factory, something like this:

class WP_UnitTest_Factory_For_User_With_Cap extends WP_UnitTest_Factory_For_User {

    function create_object( $args ) {
        $user_id = parent::create_object( $args );

        if ( $user_id ) {
            $user = new WP_User( $user_id );
            $user->add_cap( 'my_custom_cap' );
        }

        return $user_id;
    }
}

Then you can do something like this in your setUp() method:

$this->factory->user_with_cap = new WP_UnitTest_Factory_For_User_With_Cap( $this->factory );

Now in your tests you can replace $this->factory->user with $this->factory->user_with_cap in each place that you need a user with that cap to be created.


An alternative approach would be to create your own custom abstract testcase class that is a child of WP_UnitTestCase and extend all of your testcases from it. Then you could create a factory method on that class. However, this will only work if all of your testcases can be extended from a single parent, which may not be the case if you have some Ajax tests, for example.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.