Create plugin unit test case
^- inspired by keios/oc-plugin-testsuite package
This commit is contained in:
parent
5c3dd4f145
commit
0c8e813029
|
|
@ -39,7 +39,8 @@
|
|||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"tests/TestCase.php",
|
||||
"tests/UiTestCase.php"
|
||||
"tests/UiTestCase.php",
|
||||
"tests/PluginTestCase.php"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
use System\Classes\UpdateManager;
|
||||
use System\Classes\PluginManager;
|
||||
use October\Rain\Database\Model as ActiveRecord;
|
||||
|
||||
class PluginTestCase extends Illuminate\Foundation\Testing\TestCase
|
||||
{
|
||||
/**
|
||||
* @var array Cache for storing which plugins have been loaded
|
||||
* and refreshed.
|
||||
*/
|
||||
protected $pluginTestCaseLoadedPlugins = [];
|
||||
|
||||
/**
|
||||
* Creates the application.
|
||||
* @return Symfony\Component\HttpKernel\HttpKernelInterface
|
||||
*/
|
||||
public function createApplication()
|
||||
{
|
||||
$app = require __DIR__.'/../bootstrap/app.php';
|
||||
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
|
||||
|
||||
$app['cache']->setDefaultDriver('array');
|
||||
$app->setLocale('en');
|
||||
|
||||
/*
|
||||
* Store database in memory
|
||||
*/
|
||||
$app['config']->set('database.default', 'sqlite');
|
||||
$app['config']->set('database.connections.sqlite', [
|
||||
'driver' => 'sqlite',
|
||||
'database' => ':memory:',
|
||||
'prefix' => ''
|
||||
]);
|
||||
|
||||
/*
|
||||
* Modify the plugin path away from the test context
|
||||
*/
|
||||
$app->setPluginsPath(realpath(base_path().Config::get('cms.pluginsPath')));
|
||||
|
||||
return $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform test case set up.
|
||||
* @return void
|
||||
*/
|
||||
public function setUp()
|
||||
{
|
||||
/*
|
||||
* Create application instance
|
||||
*/
|
||||
parent::setUp();
|
||||
|
||||
/*
|
||||
* Rebind Laravel container in October Singletons
|
||||
*/
|
||||
UpdateManager::instance()->bindContainerObjects();
|
||||
PluginManager::instance()->bindContainerObjects();
|
||||
|
||||
/*
|
||||
* Ensure system is up to date
|
||||
*/
|
||||
$this->runOctoberUpCommand();
|
||||
|
||||
/*
|
||||
* Detect plugin from test and autoload it
|
||||
*/
|
||||
$this->pluginTestCaseLoadedPlugins = [];
|
||||
$pluginCode = $this->guessPluginCodeFromTest();
|
||||
|
||||
if ($pluginCode !== false) {
|
||||
$this->runPluginRefreshCommand($pluginCode, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable mailer
|
||||
*/
|
||||
Mail::pretend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush event listeners and collect garbage.
|
||||
* @return void
|
||||
*/
|
||||
public function tearDown()
|
||||
{
|
||||
$this->flushModelEventListeners();
|
||||
parent::tearDown();
|
||||
unset($this->app);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate database using october:up command.
|
||||
* @return void
|
||||
*/
|
||||
protected function runOctoberUpCommand()
|
||||
{
|
||||
Artisan::call('october:up');
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the test environment has loaded all the test plugins
|
||||
* natively, this method will ensure the desired plugin is
|
||||
* loaded in the system before proceeding to migrate it.
|
||||
* @return void
|
||||
*/
|
||||
protected function runPluginRefreshCommand($code, $throwException = true)
|
||||
{
|
||||
if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) {
|
||||
if (!$throwException) return;
|
||||
throw new Exception(sprintf('Invalid plugin code: "%s"', $code));
|
||||
}
|
||||
|
||||
$manager = PluginManager::instance();
|
||||
$plugin = $manager->findByIdentifier($code);
|
||||
|
||||
/*
|
||||
* First time seeing this plugin, load it up
|
||||
*/
|
||||
if (!$plugin) {
|
||||
$namespace = '\\'.str_replace('.', '\\', strtolower($code));
|
||||
$path = array_get($manager->getPluginNamespaces(), $namespace);
|
||||
|
||||
if (!$path) {
|
||||
if (!$throwException) return;
|
||||
throw new Exception(sprintf('Unable to find plugin with code: "%s"', $code));
|
||||
}
|
||||
|
||||
$plugin = $manager->loadPlugin($namespace, $path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute the command
|
||||
*/
|
||||
Artisan::call('plugin:refresh', ['name' => $code]);
|
||||
|
||||
/*
|
||||
* Spin over dependencies and refresh them too
|
||||
*/
|
||||
$this->pluginTestCaseLoadedPlugins[$code] = $plugin;
|
||||
|
||||
if (!empty($plugin->require)) {
|
||||
foreach ((array) $plugin->require as $dependency) {
|
||||
|
||||
if (isset($this->pluginTestCaseLoadedPlugins[$code])) continue;
|
||||
|
||||
$this->runPluginRefreshCommand($dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The models in October use a static property to store their events, these
|
||||
* will need to be targeted and reset ready for a new test cycle.
|
||||
* Pivot models are an exception since they are internally managed.
|
||||
* @return void
|
||||
*/
|
||||
protected function flushModelEventListeners()
|
||||
{
|
||||
foreach (get_declared_classes() as $class) {
|
||||
|
||||
if (!is_subclass_of($class, 'October\Rain\Database\Model')) continue;
|
||||
if (is_subclass_of($class, 'October\Rain\Database\Pivot')) continue;
|
||||
if ($class == 'October\Rain\Database\Pivot') continue;
|
||||
|
||||
$class::flushEventListeners();
|
||||
}
|
||||
|
||||
ActiveRecord::flushEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the plugin code based on the test file location.
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function guessPluginCodeFromTest()
|
||||
{
|
||||
$reflect = new ReflectionClass($this);
|
||||
$path = $reflect->getFilename();
|
||||
$basePath = plugins_path();
|
||||
|
||||
$result = false;
|
||||
|
||||
if (strpos($path, $basePath) === 0) {
|
||||
$result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/');
|
||||
$result = implode('.', array_slice(explode('/', $result), 0, 2));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,54 @@
|
|||
# Plugin testing
|
||||
|
||||
Plugins can be tested by creating a creating a file called `phpunit.xml` in the base directory with the following content, for example, in a file **/plugins/acme/blog/phpunit.xml**:
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
bootstrap="../../../tests/bootstrap.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Plugin Unit Test Suite">
|
||||
<directory>./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
||||
Then a **tests/** directory can be created to contain the test classes. The file structure should mimic the base directory with classes having a `Test` suffix. Using a namespace for the class is also recommended.
|
||||
|
||||
<?php namespace Acme\Blog\Tests\Models;
|
||||
|
||||
use Acme\Blog\Models\Post;
|
||||
use PluginTestCase;
|
||||
|
||||
class MessageTest extends PluginTestCase
|
||||
{
|
||||
public function testCreateFirstPost()
|
||||
{
|
||||
$post = Post::create(['title' => 'Hi!']);
|
||||
$this->assertEquals(1, $post->id);
|
||||
}
|
||||
}
|
||||
|
||||
The test class should extend the base class `PluginTestCase` and this is a special class that will set up the October database stored in memory, as part of the `setup()` method. It will also refresh the plugin being testing, along with any of the defined dependencies in the plugin registration file. This is the equivalent of running the following before each test:
|
||||
|
||||
php artisan october:up
|
||||
php artisan plugin:refresh Acme.Blog
|
||||
|
||||
# System testing
|
||||
|
||||
### Unit tests
|
||||
|
||||
Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`.
|
||||
|
|
|
|||
Loading…
Reference in New Issue