Testing Hard Dependency with AspectMock
This is another testing legacy application post. Don’t tell your client/other people to refactor, if your job is only to make tests for it, as at some situations, there are reasons to not refactor. You may have situation to test hard dependency that impossible to be mocked and stubbed. There is a library named AspectMock for it, that you can use in PHPUnit, for example.
So, to have it, you can require via composer:
composer require "codeception/aspect-mock:^0.5.5" --dev
For example, you have the following class:
namespace App; class MyController { public function save() { $user = new User(); if (! $user->save()) { echo 'not saved'; return; } echo 'saved'; } }
That bad, huh! Ok, let’s deal to tests it even you don’t really like it. First, setup your phpunit.xml to have ‘backupGlobals=”false”‘ config:
<?xml version="1.0" encoding="UTF-8"?> <phpunit colors="true" backupGlobals="false" bootstrap="bootstrap.php"> <testsuites> <testsuite name="AppTest"> <directory suffix=".php">./test</directory> </testsuite> </testsuites> <filter> <whitelist addUncoveredFilesFromWhitelist="true"> <directory suffix=".php">./src</directory> </whitelist> </filter> </phpunit>
Now, based on config above, you can create bootstrap.php
:
include 'vendor/autoload.php'; use AspectMock\Kernel; $kernel = Kernel::getInstance(); $kernel->init([ 'debug' => true, 'cacheDir' => __DIR__ . '/data/cache', 'includePaths' => [__DIR__.'/src'], ]);
Assumption: You have ‘./data/cache’ for saving cache and ‘src/’ for your source code directory, if you use your own autoloader, you can add:
// ... $kernel->loadFile('YourAutoloader.php');
as the AspectMock documentation mentioned.
Now, time to write the tests:
- Preparation
namespace AppTest; use PHPUnit_Framework_TestCase; use App\MyController; use AspectMock\Test as test; class MyControllerTest extends PHPUnit_Framework_TestCase { private $myController; protected function setUp() { $this->myController = new MyController; } protected function tearDown() { test::clean(); // remove all registered test doubles } }
-
write the test cases
class MyControllerTest extends PHPUnit_Framework_TestCase { // ... public function provideSave() { return [ [true, 'saved'], [false, 'not saved'], ]; } /** * @dataProvider provideSave */ public function testSave($succeed, $echoed) { // mock $userMock = test::double('App\User', ['save' => $succeed]); ob_start(); $this->myController->save(); $content = ob_get_clean(); $this->assertEquals($echoed, $content); // stub $userMock->verifyInvoked('save'); } // ... }
Done 😉
references:
– https://github.com/Codeception/AspectMock
– https://twitter.com/grmpyprogrammer/status/642847787713884160
– https://littlehart.net/atthekeyboard/2014/12/14/stop-telling-me-to-refactor/
2 comments