Search code examples
laravelfactoryphpspec

How to create class variables in a PHPSpec spec file that can be used for all examples in the spec


I have a PHPSpec class with many examples. I want to be able to create class variables in the spec class that can be used by any example function in the class.

Below is a very simplified version:

class ThingImTestingSpec extends ObjectBehavior
{
    private $common_variables_array = [
        'property_1' => value_1,
        'property_2' => 'Value 2'
    ];

    function it_finds_a_common_property()
    {
        $object_1 = new ConstructedObject;

        $this->find_the_common_property($object_1)->shouldReturn($this->common_variables_array['property_1']);
    }
}

The issue lies in how PHPSpec (cleverly) instantiates and references the class under test. References to $this in the spec methods actually refer to the test object, not the spec class itself.

But that means that trying to reference class variables using $this->class_variable references class variables on the test object, not the spec.

So. How to create a set of variables in the scope of the spec class itself that can be accessed by the examples at runtime?

Things I've tried:

  • Placing the class variables within a constructor – still can't be accessed by the examples
  • Using beConstructedWith – requires altering the class under test just so it can be tested. Not a clean solution.
  • When the common objects I want to reference are database records, I can reference them by id (or other properties) using Eloquent, building a collection or class object from the Model each time. This works, but is time-consuming, as I need to build the collection or object in every spec function. I'd like to build these collections and objects once, when the spec class is instantiated, and reference them throughout the class.

Things I haven't tried yet:

  • Creating a third object outside the scope of both the spec class and the class under test to house the universal objects and variables, which can be accessed by the spec class methods (the examples) at runtime. This solution could work, but it adds a layer to the specs that I'd like to avoid if there's a cleaner solution.

NB: I'm not looking for "alternatives" to going about testing in the way outlined above, unless they still suit the broader needs. The example is extremely pared down. In practice, I'm extending LaravelObjectBehavior (https://github.com/BenConstable/phpspec-laravel), creating records in a test database using the spec's constructor via Factory and Faker classes (https://github.com/thephpleague/factory-muffin), and destroying them after the test (League\FactoryMuffin\Facade::deleteSaved() in the spec's destructor). I want to be able to reference objects represented by the Model (and created by FactoryMuffin) in any number of spec functions, so I don't have to recreate these objects and collections in every spec function. And yes, I'm aware that this steps outside the realm of "spec" testing, but when an app is tethered to a model, objects that interact with the data layer are still "speccable", it can be argued.

I'm currently using phpspec 2.2.1 and Laravel 4.2


Solution

  • We currently use PHPSpec v3 in our software. Please use let method to declare common things. Quick example:

    <?php
    
    class ExampleSpec extends \PhpSpec\ObjectBehavior
    {
        private $example; // private property in the spec itself
    
        function let()
        {
            $this->example = (object) ['test1' => 'test1']; // setting property of the spec
            parent::let();
        }
    
        function it_works()
        {
            var_dump($this->example); // will dump: object(stdClass)#1 (1) { ["test1"] => string(5) "test1" }
        }
    
        function it_works_here_as_well()
        {
            var_dump($this->example); // will dump same thing as above: object(stdClass)#1 (1) { ["test1"] => string(5) "test1" }
            $this->example = (object) ['test2' => 'test2']; // but setting here will be visible only for this example
        }
    
        function it_is_an_another_example()
        {
            var_dump($this->example); // will dump same thing first two examples: object(stdClass)#1 (1) { ["test1"] => string(5) "test1" }
        }
    }