Browse Source

WIP: Craft module with module, plugin and migration support and a little cleanup

pull/3382/head
Giel Tettelaar 7 years ago
parent
commit
7a8c88c07a
  1. 5
      codeception.yml
  2. 5
      codeception.yml.example
  3. 161
      src/test/Craft.php
  4. 31
      src/test/TestSetup.php
  5. 9
      tests/_craft/config/test.php
  6. 9
      tests/acceptance.suite.yml
  7. 26
      tests/fixtures/SessionsFixture.php
  8. 12
      tests/fixtures/data/sessions.php
  9. 1
      tests/unit/helpers/AppTest.php
  10. 12
      tests/unit/web/ControllerTest.php
  11. 15
      tests/unit/web/UserTest.php

5
codeception.yml

@ -29,6 +29,9 @@ modules:
configFile: 'tests/_craft/config/test.php'
entryUrl: 'https://test.craftcms.dev/'
entryScript: 'index.php'
modules: []
migrations: []
plugins: []
cleanup: true # Should tests and fixtures be cleaned
transaction: true # Wrap in transaction
setupDb: true
dbSetup: {clean: true, setupCraft: true, setupMigrations: false} # What DB setup work do we need to do.

5
codeception.yml.example

@ -29,6 +29,9 @@ modules:
configFile: 'tests/_craft/config/test.php'
entryUrl: 'https://test.craftcms.dev/'
entryScript: 'index.php'
modules: [{'class': 'modules\base\ExampleModule', 'handle': 'example-module'}]
migrations: [{'class': 'craft\migrations\ExampleMigration', 'params': {tableName: 'exampletable'}}]
plugins: [{'class': 'craftcms\base\ExamplePlugin', 'handle': 'example-plugin'}]
cleanup: true # Should tests and fixtures be cleaned
transaction: true # Wrap in transaction
setupDb: true
dbSetup: {clean: false, setupCraft: false, setupMigrations: false}

161
src/test/Craft.php

@ -11,12 +11,16 @@ namespace craft\test;
use Codeception\Lib\ModuleContainer;
use Codeception\Module\Yii2;
use Codeception\Step;
use Codeception\Stub;
use Codeception\TestInterface;
use craft\config\DbConfig;
use craft\db\Connection;
use craft\helpers\App;
use Codeception\Lib\Connector\Yii2 as Yii2Connector;
use craft\services\Security;
use yii\base\Application;
use yii\base\Event;
use yii\base\InvalidConfigException;
use yii\base\Module;
/**
* Craft module for codeception
@ -48,6 +52,18 @@ class Craft extends Yii2
protected $triggeredEvents = [];
protected $requiredEvents = [];
/**
* The DB connection that is used to apply migrations and db setups
* @var Connection $dbConnection
*/
private $dbConnection;
/**
* A static version of the config for use on the tests/_craft/config/test.php file
* @var array
*/
public static $testConfig;
/**
* Craft constructor.
* We need to merge the config settings here as this is the earliest point in the instance's existance.
@ -65,44 +81,53 @@ class Craft extends Yii2
}
/**
* @throws \Throwable
* @throws \yii\base\InvalidConfigException
* @throws \yii\db\Exception
*/
public function _initialize()
{
// Unless told not to. Lets setup the database.
if ($this->_getConfig('setupDb') === true) {
$this->setupDb();
}
self::$testConfig = $this->_getConfig();
$this->setupDb();
parent::_initialize();
}
/**
* TODO: Plugin migrations & installations, additional migrations e.t.c.
*
* @param null $databaseKey
* @param null $databaseConfig
* @throws \Throwable
* @throws \yii\base\InvalidConfigException
* @throws \yii\db\Exception
*/
public function setupDb()
{
ob_start();
// Create a Craft::$app object
TestSetup::warmCraft();
ob_start();
$this->dbConnection = \Craft::createObject(App::dbConfig(self::createDbConfig()));
$conn = \Craft::createObject(App::dbConfig(self::createDbConfig()));
\Craft::$app->set('db', $this->dbConnection);
\Craft::$app->set('db', $conn);
$testSetup = self::createTestSetup($this->dbConnection);
// TODO: Here is where we need to add plugins and migrations to run aswell. To do that we need to rewrite the TestSetup class.
$testSetup = new TestSetup($conn);
$testSetup->clenseDb();
if ($this->_getConfig('dbSetup')['clean'] === true) {
$testSetup->clenseDb();
}
$testSetup->setupCraftDb();
if ($this->_getConfig('dbSetup')['setupCraft'] === true) {
$testSetup->setupCraftDb();
}
if ($this->_getConfig('dbSetup')['setupMigrations'] === true) {
foreach ($this->_getConfig('migrations') as $migration) {
$testSetup->validateAndApplyMigration($migration['class'], $migration['params']);
}
}
foreach ($this->_getConfig('plugins') as $plugin) {
$this->installPlugin($plugin);
}
// Dont output anything or we get header's already sent exception
ob_end_clean();
@ -110,21 +135,15 @@ class Craft extends Yii2
}
/**
* Creates a DB config according to the loaded .ENV variables.
* @return DbConfig
* @param array $plugin
* @throws InvalidConfigException
* @throws \Throwable
* @throws \craft\errors\InvalidPluginException
*/
public static function createDbConfig()
{
return new DbConfig([
'password' => getenv('TEST_DB_PASS'),
'user' => getenv('TEST_DB_USER'),
'database' => getenv('TEST_DB_NAME'),
'tablePrefix' => getenv('TEST_DB_TABLE_PREFIX'),
'driver' => getenv('TEST_DB_DRIVER'),
'port' => getenv('TEST_DB_PORT'),
'schema' => getenv('TEST_DB_SCHEMA'),
'server' => getenv('TEST_DB_SERVER'),
]);
public function installPlugin(array $plugin) {
if (!\Craft::$app->getPlugins()->installPlugin($plugin['handle'])) {
throw new InvalidConfigException('Invalid plugin handle: '. $plugin['handle'] .'');
}
}
/**
@ -150,14 +169,8 @@ class Craft extends Yii2
* For now: Remounting the DB object using Craft::$app->set() after the event listeners are called works perfectly fine.
*/
$db = \Craft::createObject(
\craft\helpers\App::dbConfig(new \craft\config\DbConfig([
'database' => getenv('TEST_DB_NAME'),
'driver' => getenv('TEST_DB_DRIVER'),
'user' => getenv('TEST_DB_USER'),
'password' =>getenv('TEST_DB_PASS'),
'tablePrefix' => getenv('TEST_DB_TABLE_PREFIX'),
'port' => '3306',
])));
\craft\helpers\App::dbConfig(self::createDbConfig())
);
\Craft::$app->set('db', $db);
}
@ -177,6 +190,28 @@ class Craft extends Yii2
->execute();
}
/**
* Gets any custom test setup config based on variables in this class.
* The array returned in here gets merged with what is returned in tests/_craft/config/test.php
*
* @return array
*/
public static function getTestSetupConfig() : array
{
$returnArray = [];
$config = self::$testConfig;
// Add the modules to the config similar to how its done here: https://github.com/craftcms/craft/blob/master/config/app.php
if (isset($config['modules']) && is_array($config['modules'])) {
foreach ($config['modules'] as $module) {
$returnArray['modules'][$module['handle']] = $module['class'];
$returnArray['bootstrap'][] = $module['handle'];
}
}
return $returnArray;
}
// Helper and to-be-directly used in test methods.
// =========================================================================
@ -202,4 +237,52 @@ class Craft extends Yii2
$this->assertTrue($requiredEvent, 'Asserting that an event is triggered');
}
/**
* @param Module $module
* @param string $component
* @param $methods
* @throws InvalidConfigException
*/
public function mockMethods(Module $module, string $component, $methods)
{
$componentInstance = $module->get($component);
$module->set($component, Stub::construct(get_class($componentInstance), [], $methods));
}
public function mockCraftMethods(string $component, $methods)
{
return $this->mockMethods(\Craft::$app, $component, $methods);
}
// Factories
// =========================================================================
/**
* Creates a DB config according to the loaded .ENV variables.
* @return DbConfig
*/
public static function createDbConfig() : DbConfig
{
return new DbConfig([
'password' => getenv('TEST_DB_PASS'),
'user' => getenv('TEST_DB_USER'),
'database' => getenv('TEST_DB_NAME'),
'tablePrefix' => getenv('TEST_DB_TABLE_PREFIX'),
'driver' => getenv('TEST_DB_DRIVER'),
'port' => getenv('TEST_DB_PORT'),
'schema' => getenv('TEST_DB_SCHEMA'),
'server' => getenv('TEST_DB_SERVER'),
]);
}
/**
* @param Connection $connection
* @return TestSetup
*/
public static function createTestSetup(Connection $connection) : TestSetup
{
return new TestSetup($connection);
}
}

31
src/test/TestSetup.php

@ -13,6 +13,7 @@ use craft\helpers\MigrationHelper;
use craft\migrations\Install;
use craft\models\Site;
use craft\web\UploadedFile;
use yii\base\InvalidArgumentException;
use yii\db\Exception;
/**
@ -66,10 +67,6 @@ class TestSetup
$_COOKIE = [];
$_REQUEST = [];
if (isset(\Craft::$app) && \Craft::$app->has('session', true)) {
\Craft::$app->session->close();
}
UploadedFile::reset();
if (method_exists(\yii\base\Event::class, 'offAll')) {
\yii\base\Event::offAll();
@ -88,16 +85,11 @@ class TestSetup
{
$tables = $this->connection->schema->getTableNames();
if ($this->connection->getIsMysql()) {
$this->connection->createCommand("SET foreign_key_checks = 0")->execute();
foreach ($tables as $table) {
$this->connection->createCommand()->dropTable($table)->execute();
}
$this->connection->createCommand("SET foreign_key_checks = 1")->execute();
} else {
// TODO: Drop all in pgsql
foreach ($tables as $table) {
MigrationHelper::dropTable($table);
}
$tables = $this->connection->schema->getTableNames();
if ($tables !== []) {
throw new Exception('Unable to setup test enviroment');
@ -112,12 +104,19 @@ class TestSetup
* @return false|null
* @throws \Throwable
*/
public function setupMigration(Migration $migration)
public function validateAndApplyMigration(string $class, array $params)
{
// Ensure our connection is used
$migration->db = $this->connection;
if (!class_exists($class)) {
throw new InvalidArgumentException('Unable to ');
}
return $migration->up();
$migration = new $class($params);
if (!$migration instanceof Migration) {
throw new InvalidArgumentException('Migration class is not an instance of craft\migrations\Migration');
}
return $migration->safeUp();
}
/**

9
tests/_craft/config/test.php

@ -14,12 +14,14 @@ $vendorPath = $basePath.'/vendor';
Craft::setAlias('@craftunitsupport', $srcPath.'/test');
Craft::setAlias('@craftunittemplates', $basePath.'/tests/_craft/templates');
Craft::setAlias('@craftunitfixtures', $basePath.'/tests/fixtures');
Craft::setAlias('@craft', $srcPath);
$customConfig = \craft\test\Craft::getTestSetupConfig();
// Load the config
$config = ArrayHelper::merge(
[
'components' => [
'config' => [
'class' => Config::class,
'configDir' => __DIR__,
@ -31,6 +33,11 @@ $config = ArrayHelper::merge(
require $srcPath.'/config/app.web.php'
);
if (is_array($customConfig)) {
// Merge in any custom variables and config
$config = ArrayHelper::merge($config, $customConfig);
}
$config['vendorPath'] = $vendorPath;
$config = ArrayHelper::merge($config, [

9
tests/acceptance.suite.yml

@ -4,10 +4,9 @@
# Perform tests in browser using the WebDriver or PhpBrowser.
# If you need both WebDriver and PHPBrowser tests - create a separate suite.
class_name: AcceptanceTester
actor: AcceptanceTester
modules:
enabled:
- \craft\test\Craft:
part: init
cleanup: false # don't wrap test in transaction
entryScript: index-test.php
- PhpBrowser:
url: http://localhost/myapp
- \Helper\Acceptance

26
tests/fixtures/SessionsFixture.php

@ -0,0 +1,26 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/
namespace craftunit\fixtures;
use craft\records\Session;
use craft\test\Fixture;
/**
* Unit tests for SessionsFixture
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @author Global Network Group | Giel Tettelaar <giel@yellowflash.net>
* @since 3.0
*/
class SessionsFixture extends Fixture
{
public $dataFile = __DIR__.'/data/sessions.php';
public $modelClass = Session::class;
}

12
tests/fixtures/data/sessions.php

@ -0,0 +1,12 @@
<?php
return [
[
'id' => '1000',
'userId' => '1',
'token' => 'PRRG3Xdr-qfU7Mk75T81WzKnZV5NQp50pVnQClnCbmE5fSPOLqXKYqYdyJnrtalLOGlLb2TmDyNEmE-j_oZiAn8UGdHBYcbcOAsL',
'dateCreated' => '2017-12-29 14:54:34',
'dateUpdated' => '2017-12-29 14:54:34',
'uid' => '307a18cf-4af8-4a95-95ca-be5cbd4ab753'
]
];

1
tests/unit/helpers/AppTest.php

@ -86,6 +86,7 @@ class AppTest extends \Codeception\Test\Unit
['2\0\0', '2\0\0'],
['2', '2+2+2'],
['2', '2-0-0'],
['~2', '~2'],
['', ''],
['\*v^2.0.0(beta)', '\*v^2.0.0(beta)'],

12
tests/unit/web/ControllerTest.php

@ -156,6 +156,18 @@ class ControllerTest extends Unit
'index.php',
$this->controller->redirect(null)->headers->get('Location')
);
// Absolute url
$this->assertSame(
'https://craftcms.com',
$this->controller->redirect('https://craftcms.com')->headers->get('Location')
);
// Custom status code
$this->assertSame(
500,
$this->controller->redirect('https://craftcms.com', 500)->statusCode
);
}
// Helpers

15
tests/unit/web/UserTest.php

@ -10,16 +10,11 @@ namespace craftunit\web;
use Codeception\Stub;
use Codeception\Test\Unit;
use craft\db\Query;
use craft\elements\User;
use craft\helpers\StringHelper;
use craft\services\Config;
use craft\services\Security;
use craft\test\TestCase;
use craft\web\Session;
use craftunit\fixtures\SessionsFixture;
use yii\web\Cookie;
/**
* Unit tests for UserTest
@ -175,7 +170,7 @@ class UserTest extends TestCase
$this->user->setIdentity($this->userElement);
$this->assertFalse($this->user->startElevatedSession($passwordHash));
// Ensure password validation returns false
// Ensure password validation returns true
$this->passwordValidationStub(true);
// If we set this to 0. It should return true
@ -209,7 +204,7 @@ class UserTest extends TestCase
*/
private function passwordValidationStub($returnValue)
{
\Craft::$app->set('security', Stub::make(Security::class, ['validatePassword' => $returnValue]));
$this->tester->mockCraftMethods('security', ['validatePassword' => $returnValue]);
}
/**
@ -219,9 +214,9 @@ class UserTest extends TestCase
*/
private function ensureSetSessionIsOfValue($value)
{
\Craft::$app->set('session', Stub::make(Session::class, ['set' => function($name, $val) use ($value) {
$this->tester->mockCraftMethods('session', ['set' => function($name, $val) use ($value) {
$this->assertSame($value, $val);
}]));
}]);
}
/**
@ -231,7 +226,7 @@ class UserTest extends TestCase
*/
private function sessionGetStub($returnValue)
{
\Craft::$app->set('session', Stub::make(Session::class, ['get' => $returnValue]));
$this->tester->mockCraftMethods('session', ['get' => $returnValue]);
}
private function getUser()

Loading…
Cancel
Save