Working with BDD and resources in Sylius - Part 2, PHPSpec

Mikołaj Król, 27 cze 2017

As we are going to use a new tool, let's start with some basic setup.

Create new phpspec.yml.dist file in your AppBundle directory with the following content:

suites:
    main:
        namespace: AppBundle
        psr4_prefix: AppBundle
        src_path: .

And then once again create a new phpspec.yml.dist in your project root:

suites:
    AppBundle: { namespace: AppBundle, psr4_prefix: AppBundle, spec_path: src/AppBundle, src_path: src/AppBundle }

Let's run the bellow command:

$ bin/phpspec desc AppBundle/Entity/Book

This should create a new file named BookSpec.php in src/AppBundle/spec/Entity directory.

Now run the $ bin/phpspec run command and confirm that you want the phpspec to create the Book entity class for you. You should end up with a BookSpec class which looks like this:

namespace spec\AppBundle\Entity;

use AppBundle\Entity\Book;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class BookSpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType(Book::class);
    }
}

And an empty Book entity class.

Let's refactor it a little bit. Create a BookInterface in your AppBundle\Entity namespace:

namespace AppBundle\Entity;

use Sylius\Component\Resource\Model\ResourceInterface;

interface BookInterface extends ResourceInterface
{
    /**
    * @param string $title
    */
    public function setTitle($title);

    /**
    * @return string
    */
    public function getTitle();
}

Now jump back to the BookSpec and write the Book class specification:

namespace spec\AppBundle\Entity;

use AppBundle\Entity\Book;
use AppBundle\Entity\BookInterface;
use PhpSpec\ObjectBehavior;
use Sylius\Component\Resource\Model\ResourceInterface;

final class BookSpec extends ObjectBehavior
{
    const TITLE = 'Harry Potter';

    function it_is_initializable()
    {
        $this->shouldHaveType(Book::class);
        $this->shouldHaveType(BookInterface::class);
        $this->shouldHaveType(ResourceInterface::class);
    }

    function it_allows_access_via_properties()
    {
        $this->setTitle(self::TITLE);
        $this->getTitle()->shouldReturn('Harry Potter');
    }
}

As we are going to register the Book entity as a Sylius resource, we have to make sure it implements the ResourceInterface and also as we implemented the BookInterface for this model class, it also should be described in the spec file.

  The reason I am not using camelCase notation and the title is defined as const is beautifully described in this Sylius documentation part. There are also few other valuable notes that I encourage you to read (if you are a lazy dev, which is good, it can be found on the bottom).

Fire $ bin/phpspec run command once again. PHPSpec will ask you twice you to approve it to generate missing Book class methods for you. After agreeing, you will see that obviously, your spec is not passing. Let's make it work by implementing missing fields and method logic as well as interface implementation from the Book class.

namespace AppBundle\Entity;

class Book implements BookInterface
{
    /**
     * @var int
     */
    private $id;

    /**
     * @var string
     */
    private $title;

    /**
     * {@inheritdoc}
     */
    public function getId()
    {
        return $this->id;
    }

    /**
    * {@inheritdoc}
    */
    public function setTitle($title)
    {
        $this->title = $title;
    }

    /**
    * {@inheritdoc}
    */
    public function getTitle()
    {
        return $this->title;
    }
}
The reason I am not using final keyword in the class definition is that the class will be extended by Doctrine while building the application cache.

Now your $ bin/phpspec run command should return beautiful green output. Congratulations!

Finally we can move forward to the right implementation.

Sylius BDD - Behat, 27 cze 2017 Sylius BDD, ResourceBundle, 27 cze 2017