1

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

Rick Gladwin
  • 4,225
  • 1
  • 17
  • 21

2 Answers2

2

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" }
    }
}
Damian Dziaduch
  • 2,107
  • 1
  • 15
  • 16
1

Found the answer. Explicitly declare the class variables as static and they can be accessed by the methods in the spec class:

class ThingImTestingSpec extends ObjectBehavior
{
    private static $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']);
    }
}

This is working for arrays as well as objects that represent database records built using Eloquent, e.g.

class LaravelAppClassImTestingSpec extends LaravelObjectBehavior
{
    private static $Order_1;

    function __construct()
    {
        $Order_1 = \Order::find(123);
    }

    function it_tests_a_thing()
    {
        //(the method has access to the static class variable via
        //$this::$Order_1
    }
}
Rick Gladwin
  • 4,225
  • 1
  • 17
  • 21
  • **Important!** Make sure you name the class variables something different in the spec vs in the class under test. e.g. $spec_variable_1 vs $variable_1 in order to differentiate them. This is important, for example, when using variables to hold relative filepaths, which will be different for the spec vs the class under test. – Rick Gladwin Oct 04 '17 at 16:22