How to use PHPSpec with Magento

Posted May 20, 2014

How to use PHPSpec with Magento e-commerce platform

More and more during working hours and evening hours I have been using the great PHPSpec PHPSpec emergent design tool. I had read about spec BDD alot and used Behat during normal operation where I could write features before code and red green refactor. Yet this was at a much higher level and did not help guide my code and design implementation.

During my first code kata I saw the real power and beauty of PHPSpec, I could describe behaviour using the tool and with the helper functions code was auto generated for me to then complete with the required domain logic. I started to design my code with less coupled logic and a cleaner API.

However when I moved back to Magento projects a new level of complexity was introduced that makes testing harder but not impossible. The main issue I faced was around mocking core Magento calls so that I could test my logic without having to test model and helper implementations.

So assuming you have a working Magento installation on your machine ( I wont go through how to get this set up if you dont but there are many Vagrant boxes on github that you can use ). We will start by creating a composer.json file in the root of the project:

{
    "require": {
        "php": ">=5.3.0"
    },
    "require-dev": {
        "magetest/magento-phpspec-extension": "dev-develop",
        "bossa/phpspec2-expect": "dev-master"
    },
    "config": {
        "bin-dir": "bin"
    },
    "autoload": {
        "psr-0": {
            "MageTest\\PhpSpec\\MagentoExtension": "src",
            "": [
                "app/code/local",
                "app/code/community",
                "app/code/core",
                "app",
                "lib"
            ],
            "Mage" : "app/code/core"
        }
    },
    "minimum-stability": "dev"
}

Some key points of interest within the composer.json file is that we are requiring the Magento-phpspec-extension that will also install PHPSpec for us. I also like to install PHPSpec2-expect as it provides some useful expectation functions. We also autoload the extension as well as key Magento API points.

You should have composer installed on your system but if not you can find instructions to download it from Get Composer. Once you have it installed you should be able to run:

php composer.phar install

Once composer has finished installing you will have two new directories: bin and vendor. Bin is where the executable files for phpspec are located and vendor contains all of the source files for the required applications.

Next we need to create a new file in the root of the project called phpspec.yml this file is what PHPSpec will read to load the Magento Extension:

extensions: [MageTest\PhpSpec\MagentoExtension\Extension]
mage_locator:
  spec_prefix: 'spec'
  src_path: 'app/code'
  spec_path: 'spec/app/code'

We should now be able to run the following command without errors:

./bin/phpspec

The next logical steps are to create a module that we can design using PHPSpec and the Magento extension. The benefit of using the Magento extension is that we now have access to a Magento aware extension. By this I mean we can use PHPSpec commands to describe models, blocks controllers and helpers.

So lets define what our module should do. For simplicity our module should be basic. We will create a module that will either greet the user with there logged in name or guest ( I know magento does this by default but its an example we can use ). Keeping it simple we should only need a block and a template file with some XML to keep Magento happy.

We need to start by first creating our module basics within Magento. So lets create a new file under app/etc/modules/Phpspec_Helloworld

<?xml version="1.0"?>
<config>
    <modules>
        <PHPSpec_Helloworld>
            <active>true</active>
            <codePool>local</codePool>
        </PHPSpec_Helloworld>
    </modules>
</config>

Currently MageSpec only supports the local code pool so we will have to create our module in that location. Lets create the basic structure required under app/code/local/Phpspec/Helloworld we will need

etc/config.xml
Block
Helper
``

The config.xml file should contain

1.0.0 PHPSpec_Helloworld_Block

This is enough manual work for the moment that will allow us to use PHPSpec and to start designing our module. We can now start by describing
our Block file

./bin/phpspec describe:block phpspec_helloworld/greet


This should generate the following:

Specification for Phpspec_Helloworld_Block_Greet created in ~/spec/app/code/local/Phpspec/Helloworld/Block/GreetSpec.php.


This has now generated us the required specification class that we can use to spec our module. Now we can run:

./bin/phpspec run


We will be prompted to create the class as it does not exist. Press Y and we can continue. Looking at the file we created we should see ( spec/app/code/local/Phpspec/Helloworld/Block/GreetSpec.php )

<?php

namespace spec;

use PhpSpec\ObjectBehavior; use Prophecy\Argument;

class Phpspec_Helloworld_Block_GreetSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType(‘Phpspec_Helloworld_Block_Greet’); } }


This is a basic specification that just checks that the class we have is of the type we would expect. Lets add some more behaviour to this specification. We want to be
able to call a method called Greet and expect it to return us a generic non logged in message. Remember at this point we are only writing about the behaviour we expect
we are not writing any production code yet.

<?php function it_should_greet_non_logged_in_users_with_a_generic_message() { $this->greet()->shouldReturn(‘Hello guest, Please register with us for special offers’); }


Its important to remember that in the above example when we refer to $this you should consider $this to be the actual block file.

Running PHPSpec will return:

./bin/phpspec run

Do you want me to create Phpspec_Helloworld_Block_Greet::greet() for you? [Y/n] Y

Method Phpspec_Helloworld_Block_Greet::greet() has been created.


What we have done is defined a new method in the spec "greet" that should exist in the Block class. When we ran PHPSpec the method did not exists
so PHPSpec has created it for us. We should now run the tests again and we will see a failing outcome. This is because "greet" does not return
the string value PHPSpec is expecting. Following the TDD cycle we can fix this quickly by adding the following to the block file:

<?php public function greet() { return ‘Hello guest, Please register with us for special offers’; }


Running PHPSpec again we now have a passing test but what we have developed does not match what we expected. Lets add a new specification to the spec file to highlight what
logic is still required. This time the behaviour we expect is that as a logged in user you will see a different message.

<?php function it_should_greet_logged_in_users_with_a_message() { $this->greet()->shouldReturn(‘Hello registered user’); }


Running PHPSpec now will pass the first test but the second will fail because it it not returning the string value for a logged in user. We now need to refactor and think
about the code that we are going to develop to ensure it's testable. We are going to have to use Magento core functionality to obtain the current
state of a user and this code needs to be mocked.

Why do we need to mock the Magento core code ? I see two main reasons for mocking this Magento core code and these are:

* For this implementation we are going to look at a session and just by running PHPSpec we will not have a session started.
* We are not testing that Magento core code works. We did not develop this code so we should isolate it so we can test our own code.

So lets put this in a adapter file and inject it via the construct so that we can easily mock the expected outcome.

Lets start by creating the adapter directory ( Adapter ) and create a new file within called state.php with the contents:

<?php class Phpspec_Helloworld_Adapter_State { public function getUserState() { if(Mage::getSingleton(‘customer/session’)->IsLoggedIn()){ return true; }else{ return false; } } }


Now in the Block file lets update the code so that we can inject this new class into the object and mock where required. The benefit
of implementing dependency injection in this way is that we can now isolate Magento core code away from custom developed code making
the system more testable. We could easily use the Magento helper files as an option without introducing a new folder and class
yet I feel that implementing this clutters the true meaning of what a helper file should be. That being a common location for Magento
code between components within Magento. We might want to test some of the logic contained within helpers and then we would need
to isolate core code in another location. So its cleaner to start with an adapter file that isolates Magento owned code.

<?php private $_stateAdapter;

/**
 * @param array $services
 */
public function __construct(array $services = array())
{
    if (isset($services['state_adapter'])) {
        $this->_stateAdapter = $services['state_adapter'];
    }
    if (!$this->_stateAdapter instanceof Phpspec_Helloworld_Adapter_State) {
        $this->_stateAdapter = new Phpspec_Helloworld_Adapter_State();
    }
} ```

This will allow us to mock out the call to the state adapter with the required results we want in the code without having to check the session / database state of the user within Magento.

Lets update the greet method with some more logic before we look back at the spec. What we are doing here is getting the current instance of the state adapter object, If this is from PHPSpec its the mocked object if this is being called from Magento then it is a new instance of the PHPSpec Adapter class. We then call the method to get the current user state and return strings based on the return boolean value.

<?php
public function greet()
{
  $state = $this->_stateAdapter;

  if ($state->getUserState()) {
      return 'Hello guest, Please register with us for special offers';
  } else {
      return 'Hello registered user';
  }

}

Now if we move back to the spec file we can inject the new dependency into the specification. PHPSpec has a let method that is the the setUp alternative from PHPUnit. It will allow us to pass arguments into the constructor in this instance we are telling PHPSpec to construct the spec with a mocked instance of the adapter class.

<?php
function let(\Phpspec_Helloworld_Adapter_State $adapter)
{
  $this->beConstructedWith(array('state_adapter' => $adapter));
}

function it_should_greet_non_logged_in_users_with_a_generic_message($adapter)
{
  $adapter->getUserState()->willReturn(false);
  $this->greet()->shouldReturn('Hello registered user');
}

function it_should_greet_logged_in_users_with_a_message($adapter)
{
  $adapter->getUserState()->willReturn(true);
  $this->greet()->shouldReturn('Hello guest, Please register with us for special offers');
}

For each of the specifications or behaviour that we are describing we now can pass in $adapter that is the mocked instance of the PHPSpec state adapter. By calling the method within the adapter we can set what value we would expect it to return, So for these examples we are setting for logged in user true and non logged in user false. This then allows the logical statement within the block to have a value it can work with.

Now when we run PHPSPec we should see all the tests passing. The next steps within the modules development would be to create the layout.xml file and the blocks template so that we can insert the block into the page and call our greet method to see the output embedded within the page. However we are testing that the block will return us the correct string when the end user is in different states so there is no real need to or a method to test that the template will output some content using PHPSpec. What we could do is write Behat scenarios as features for this module that would using web drivers load the pages in different states and check the markup generated to ensure that the string is present on the page. This is beyond this tutorial but I hope to follow up soon on a guide to getting up and running with MageBehat.

I hope you found this guide useful. I dont cover anything more than basic implementation of PHPSpec within Magento but you should be able to see some of the design discussion I hve made during the development and how using PHPSpec first has guided me to isolate core code away from my own testable code.

If you have any questions please feel free to raise an issue on github against this post and I will try and answer as many as I can.

You may also find these related posts interesting: Developer Book Club All hail Xdebug and lets let var dump die A new look and a cleanup of content plus good bye github pages. Docker and Docker Sync