Since Magento 1 doesn't use composer out of the box, I don't think it makes a big difference if you install phpunit using composer or just download the phar version.
If you already use composer to manage other third party modules or libraries in your site, then composer probably makes the most sense.
Unless you use PHP7 you will be limited to an old version of phpunit though (that's why I linked to the 4.8 version above).
Integration Tests vs/and/or Unit Tests
Since Magento 1 is such a heavy weight application, it makes sense to separate the phpunit bootstrap into one for integration and one for unit tests.
The unit test bootstrap only needs to initialize the autoloader, while the integration test bootstrap needs to initialize the whole application environment including configuration loading and the db connection.
Because of that integration tests in Magento tend to run a lot slower then unit tests (even more so then in other applications).
Bootstrapping Magento into phpunit
The autoloader of Magento is not PSR-0 compliant as it throws an exception if it can't find the file a class is located in. This breaks some usages of
class_exists in phpunit. There are several possible (if hacky) workarounds:
- Unregister the Magento autoloader, wrapping
\Varien_Autoload::autoload() in a decorator ignoring exceptions thrown within, and register the wrapper as a new autoloader. This has a low chance of conflicts with third party libraries who register autoloaders and depend on a specific autoloader order.
- Use a custom error handler wrapping the one built into Magento 1. The custom error handlers swallows errors triggered by the Magento autoloader. This is the solution that Raphael's test framework uses. This seems to be the most compatible with other third party extensions.
- Use the include path hack to override
\Varien_Autoload::autoload() to not throw the error if the file doesn't exist. This however conflicts with several modules that also override the same class. I don't use this approach myself.
To avoid errors from the session being started during tests simply set
$_SESSON =  in the bootstrap.
Set a custom response object via
Mage::app()->setResponse($testResponse) that extends the real one but does not send output or headers.
To reinitialize Magento between integration tests that completely change the runtime state, use
Mage::reset(); Mage::app(). Note that after that the error handler will have to be re-decorated.
For DB fixtures I tend to use the regular models in fixture methods to create fixtures, e.g.
createSimpleProduct($sku). Like Raphael said, use
tearDown() to wrap the test in a transaction that gets rolled back after the test (for example
For store configuration fixtures, I tend to set up in-memory only fixtures using
EcomDev_PHPUnit extension also provides the option to create DB fixtures using yaml files, but for myself I find those harder to maintain compared to fixtures created using model classes. YMMV.
The registry can be used to inject test doubles for objects created via
Some other central objects can be set on
Mage::app() (e.g. the request).
To replace classes created via
Mage::getResourceModel() with test doubles, a custom config object wrapper has to be used. See this example in Raphael's test framework how that can be accomplished.
Once Magento is bootstrapped, pretty much everything can be tested rather nicely. Be prepared to create deep mocks because of the large amount of method chaining the core code uses though.
Even though the setup is hacky, it works well and I find the tests give me a lot of confidence and value, pretty much comparable to a test suite for a Symphony app.