简体   繁体   中英

PHPUnit mock class that has named static constructor

Given I have a FruitSalad class (the system under test):

class FruitSalad
{
    protected $fruits = [];

    public function addFruit(Fruit $fruit)
    {
        $this->fruits[] = $fruit;
        return $this;
    }
}

And I have a Fruit class:

class Fruit
{
    public static function withName($name)
    {
        $instance = new MyDependencyClass();
        $instance->name = $name;
        return $instance;
    }
}

A trivial example, however you can see that the Fruit class uses a named static constructor, and the addFruit() method on the FruitSalad class type hints Fruit as its expected parameter.

When writing a test for addFruit() , I need to mock the Fruit class.

function test_it_can_add_a_fruit_to_its_list_of_fruits()
{
    $fruit = $this->getMockBuilder('Fruit')
        ->disableOriginalConstructor()
        ->getMock();

    $this->fruitSalad->addFruit($fruit);
    // Do some assertion.
}

This creates a simple mock of the Fruit class, but I want to instantiate it via the withName() static method - and I do not want to expose a setter for the name property.

How can I create a mock for Fruit using the static named constructor?

PHPUnit used to support mocking static methods, but since PHPUnit 4.0 it's omitted. I see four options here:

1. Don't mock the method at all

You could just call the method and use it's logic, although you'd test the static method as well if you do and normally that's something you should avoid when writing unit tests.

2. Change the class

Ask yourself if this method really needs to be static and if not, change the class to test it properly. There are quite some use cases where it's better to change some of your architecture in order to write proper tests.

3. Use a spy class

Spy classes are classes that extend a class that you would usually mock, but implement some logic for testing the configuration of a class or the dependency of a tested method to another method. In the very most cases this can be avoided by mocking the class properly. Spies are simply your work around if mocks are not enough, there are very few cases in which you really need them.

However, in this case a spy could be used to overwrite a static method as a work around:

class FruitSpy extends Fruit
{

    public static $return;

    public static $name;

    public static function withName($name) {
        $expected = self::$name;
        if($name == $expected) {
            return self::$return;
        } else {
            throw new \RuntimeException("FruitSpy::withName(): Parameter 0 was $name, $expected expected");
        }
    }

}

This example checks for the correct $name and, if it's correct, returns your defined return. You'd use it like this in your test:

$fruitSpy = new FruitSpy();
$fruitSpy::$name = "Banana";
$fruitSpy::$return = new \stdClass();

$this->fruitSalad->addFruit($fruitSpy);

Not exactly a clean solution, but the only way I see if you absolutely positively don't want to change other code than the test code.

Again, you should think about changing the static method to a casual method if you need to do something like this.

4. Use PHPUni 3.*

You could simple use a deprecated version of PHPUnit to use this method. Not a preferred way either.

Conclusion

I don't see a clean way to mock a static method and ::staticExpects() was removed for a reason in 4.0

How can I create a mock for Fruit using the static named constructor?

You can't. Mocks are created by using a mocking framework.

Anyway it does not matter how mocks are created but, instead, how they behave, because they're external to the class being tested.

Just configure the mock so that it behaves the same way a real Fruit instance would when created using Fruit::withName .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM