Unit testing allows you to give yourself a guarantee that your code is stable and working like it’s supposed to. Ongoing unit testing helps to identify problems early on in order to stop compound errors occurring further down the line.
Here’s a simple guide to unit testing with Magento.
The module used in the test can be found here.
[nectar_dropcap color=”#00629b”]T[/nectar_dropcap]his tutorial makes the following assumptions:
- You are using the VirtualBox environment for local development
- You are on a Windows machine
- You are using PHPStorm for development
If these assumptions are correct, you shouldn’t have any troubles getting set up and running. If any of the above assumptions are incorrect, feel free to let us know if you run into trouble. It should go without saying that this should not be done on a production server.
To begin, install PHPUnit on the VirtualBox server:
sudo apt install phpunit
While that’s installing, let’s get the Magento installation ready for testing. In this case, I’m using a vanilla Magento installation in a folder called magento:
mkdir -p /var/www/html/magento/tests/unit/testsuite
Inside the /var/www/html/magento/tests/unit/ directory, you’re going to add two files: autoload.php and phpunit.xml. Copy and paste the following into each:
autoload.php
<!--?php // set the include path for convenience ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . dirname(__FILE__) . '/../../app' . PATH_SEPARATOR . dirname(__FILE__)); //Set custom memory limit ini_set('memory_limit', '512M'); //Include Magento libraries require_once 'Mage.php'; //Start the Magento application Mage::app('default'); //Avoid issues "Headers already send" session_start();
phpunit.xml
testsuite ../../app/code/local
You should now have a directory structure resembling the following in your Magento root folder:
The actual tests for your module belong in the testsuite directory, following the same Company/Module directory structure used in Magento. In the case of the Accolade_Button module for example, the folder structure looks like this:
As a quick introduction to unit testing, let’s write a quick test for the Accolade_Button_Helper_Api::getApiUrl() function. Here’s the function:
Accolade_Button_Helper_Api::getApiUrl()
protected $_apiUrl = 'https://your.bt.tn/serves/'; protected $_version = 'v1'; /** * Retrieve the merchant name from configuration * * @return string accolade/button/merchant_name */ public function getMerchant() { return Mage::getStoreConfig('accolade/button/merchant_name'); } /** * Build the API URL unique to the merchant * * @return string URL */ public function getApiUrl() { return $this->_apiUrl . $this->getMerchant() . '/' . $this->_version . '/'; }
Notice that this function also calls another function. We have many ways of dealing with this situation but to keep things simple, let’s just write the test for that other function first. We can begin by creating the Accolade_Button_Helper_ApiTest class. Take a look at the following code:
ApiTest.php
<!--?php class Accolade_Button_Helper_ApiTest extends PHPUnit_Framework_TestCase { protected $helper; public function setUp() { Mage::app(); $this->helper = Mage::helper('accolade_button/api'); } }
There are a couple of important things to note here:
- The file name is the exact same, just with “Test” appended to it
- The class name follows the same pattern
- The class extends the PHPUnit_Framework_TestCase class
- We instantiate the Mage store in the setUp() function, along with any variables we plan to use in each function
Now to add in the test:
public function testGetMerchant() { Mage::getConfig()->saveConfig('accolade/button/merchant_name', 'test-merchant')->reinit(); $this->assertEquals('test-merchant', $this->helper->getMerchant(), 'Merchant name'); }
I won’t dive into the details of what’s going on here because it’s pretty generic unit testing syntax and there are hundreds of resources available to learn unit testing in general. Just note that the function is public and is basically just “test” + the function name. In order to run this, there are a few settings we will have to configure first. Go ahead and fire up PHPStorm if you haven’t already done so and open the settings. Under Languages & Frameworks → PHP, click on the ellipses next to the interpreter:
This will open up another window where you can add PHP interpreters. We’re going to add a remote interpreter for the PHP executable on the VirtualBox. Click on the green + symbol in the upper left corner, then click “Remote…”, and then click the SSH Credentials radio button:
Enter in the credentials you use to log into the remote machine and click OK. PHPStorm will attempt to connect to your virtual machine and, if successful, close that dialog and return you to the previous screen. From here, click the ellipses next to the Path mappings setting and map your local Magento root to the remote one like so, by clicking on the green + symbol in the upper right corner:
You may click OK here as well to close that. From here, navigate to the Languages & Frameworks → PHP → PHPUnit menu, and click the green + symbol again to configure PHPUnit. Select “By Remote Interpreter…” and then select the interpreter we configured previously:
Click OK, and then set the following settings (click to enlarge):
Return to our ApiTest.php file, and then run it! The default keyboard shortcut for this is Ctrl + Shift + F10. You should see results like so:
It passed! Woo! Now that we know that this function is working as expected, let’s get back to writing the test for the first function. Add this function to our ApiTest.php file:
public function testGetApiUrl() { // First, set the store config so we don't have any surprises Mage::getConfig()->saveConfig('accolade/button/merchant_name', 'test-merchant')->reinit(); // This is what we're expecting to get: $expected = 'https://your.bt.tn/serves/test-merchant/v1/'; // So let's test! $this->assertEquals($expected, $this->helper->getApiUrl(), 'API URL'); }
Save it, and then run the tests again:
And we get the green bar again! That’s all for this tutorial, so be sure to read up on PHPUnit so that you are familiar with its syntax and let us know if you get stuck!