How to use PHPSpec and Magento 2 a quick start
Using PHPSpec in Magento 1 was never an easy task. There was no Dependency Injection so it made it hard to actually inject dependencies into our classes. This in turn had the knock on effect of forcing us to actually test the classes our subjects were being used in. Whats the big deal I hear you say? Well. Why should we have to bootstrap the entire framework just to unit test a piece of behavior we are developing? Most of the time ( and if we are designing ) our code well we should be de coupled from the framework so there is a clear divide between business logic and framwework code. more on this in future posts
Now this has all changed, Setting up and using PHPSpec in our code is simples. Lets start by using composer :) Yes composer to install the dependencies into our project.
- Quick side note. I am using the awesome Docker setup from MageInferno *
- Another note, This guide follows up on the setup we did in the last guide Found here
Open up your
composer.json manifest for the project ( if you are using the docker images I am then the sources files are located in
src folder.) Hunt down the line that defines
require-dev and add in:
Save the file and a quick
composer update and we will have
bin/phpspec at our disposal.
Now we have our unit testing tool installed lets have a talk about what we are going to build. Well I wanted to show something that is simple and as we are learning PHPSpec and de coupled design here I didn’t want to have to cover lots of Magento 2 concepts. So we are going to add a new command to
bin/magento that will say Hello $name; not what you might call rocket science and probably violates all of the design principles for over engineering but meh.
Open up your terminal and run
./bin/phpspec describe Jcowie/HelloWorld/Model/HelloWorld what we are saying here is that we want to describe a new class called HelloWorld. Now if you followed the previous guide and you should then you will notice that I am describing code within
HelloWorld yet the module we created previously was called
HelloWorldModule. Now the reason is because I want to show how we can decouple framework agnostic code from code that is bound to the framework.
Lets run the spec
./bin/phpspec r select yes to get all the code generation. Lets look at the output.
Great so looking at the output PHPSpec has found that the class HelloWorld did not exist under
app/code and it has created this for us. Lets write our first spec also known as a unit test. Open up the spec file and add in:
To find out more about PHPSpec and how we write specs Allan MacGregor has a great MageCast on this. But what we are saying is that when we call the method
sayHello it should return us
Running PHPSpec now (
./bin/phpspec r) we should see some more information:
Great what is this saying. Well PHPSpec cant find the method in the class for
sayHello so it has asked if we want PHPSpec to create it for us. Finally it ran the tests and all failed as the method expected us to return a string but got null. In the Red Green Refactor TDD loop its now time to write some code. Open up the Model class in
HelloWorld namespace and to the method
return 'Hello World'
Great so now running PHPSpec we have all passing tests but this is no good as its not actually being used in our class. Lets fix that by creating a new Command Class. Add a new folder to
Commands and create
GenerateCommand class with the following contents:
Most of this is just boiler plate Symfony console command code. What is interesting to us is the
__construct() function where we are injecting our dependency in
Pretty simple stuff but we inject into our class a instance of our plain ol PHP class that has the single method and assign this into a variable so that when we output data to the CLI we can call
sayHello without having to create a new instance of the class. This makes testing of classes so much easier as we can if wanted test this Command class by replacing what could be more complex in the dependency injected. E.g. if its a remote webservice that we are calling. Yet I would consider this to be more of a integration test that a unit test.
To complete the module we need to add a final piece of M2 configuration that is the DI.xml file and this is used to register our command on the command registration stack
All that is worth noting here is that we inject into
moduleCommand the reference to our GeneratorCommand so that when someone runs
bin/magento our command can be added to this list. I will look at exploring the DI in lots more posts and videos soon.
What I want you to take from this most is that its really easy to de couple code from Magento 2 and create more testable objects that are not constrained by the limitations of the framework. This helps us think about Unit testing, Integration testing and Functional testing in new ways as we can have a clear seperation of our domain an inject that into smaller controllers and framework containers.
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