diff --git a/docs/07-AdvancedUsage.md b/docs/07-AdvancedUsage.md index 8daa6825b8..6b44e6ae86 100644 --- a/docs/07-AdvancedUsage.md +++ b/docs/07-AdvancedUsage.md @@ -65,6 +65,86 @@ As you see, Cest class have no parents like `\Codeception\TestCase\Test` or `PHP Also you can define `_failed` method in Cest class which will be called if test finishes with `error` or fails. +## Dependency Injection + +Codeception supports simple dependency injection for Cest and \Codeception\TestCase\Test classes. It means that you can specify which classes you need as parameters of special `_inject()` method, and Codeception will automatically create respective objects and invoke this method, passing all dependencies as arguments. This can be useful when working with Helpers, for example: + +```php +signUp = $signUp; + $this->navBar = $navBar; + } + + public function signUp(AcceptanceTester $I) + { + $I->wantTo('sign up'); + + $this->navBar->click('Sign up'); + $this->signUp->register([ + 'first_name' => 'Joe', + 'last_name' => 'Jones', + 'email' => 'joe@jones.com', + 'password' => '1234', + 'password_confirmation' => '1234' + ]); + } +} +?> +``` + +Just make sure that all Helpers can be autoloaded. + +Example of Test class: + +``` +math = $math; + } + + public function testAll() + { + $this->assertEquals(3, $this->math->add(1, 2)); + $this->assertEquals(1, $this->math->subtract(3, 2)); + } +} +?> +``` + +It is usually preferable to make `_inject()` method `protected` or `private` for not to confuse it with `public` test methods. `_inject()` will be invoked just once right after creation of TestCase object (either Cest or Test). + +Moreover, Codeception can resolve dependencies recursively (when `A` depends on `B`, and `B` depends on `C` etc.) and handle parameters of primitive types with default values (like `$param = 'default'`). Of course, you are not allowed to have *cyclic dependencies*. + ### Before/After Annotations You can control execution flow with `@before` and `@after` annotations. You may move common actions into protected (non-test) methods and invoke them before or after the test method by putting them into annotations. It is possible to invoke several methods by using more than one `@before` or `@after` annotation. Methods are invoked in order from top to bottom. diff --git a/src/Codeception/Command/Bootstrap.php b/src/Codeception/Command/Bootstrap.php index 88b944f3d6..a7cff221c7 100644 --- a/src/Codeception/Command/Bootstrap.php +++ b/src/Codeception/Command/Bootstrap.php @@ -28,7 +28,7 @@ class Bootstrap extends Command // defaults protected $namespace = ''; protected $actorSuffix = 'Tester'; - protected $helperDir = 'tests/_support'; + protected $supportDir = 'tests/_support'; protected $logDir = 'tests/_output'; protected $dataDir = 'tests/_data'; @@ -49,7 +49,9 @@ public function getDescription() public function execute(InputInterface $input, OutputInterface $output) { - $this->namespace = rtrim($input->getOption('namespace'), '\\'); + if ($input->getOption('namespace')) { + $this->namespace = trim($input->getOption('namespace'), '\\').'\\'; + } if ($input->getOption('actor')) { $this->actorSuffix = $input->getOption('actor'); @@ -111,7 +113,7 @@ public function createGlobalConfig() 'tests' => 'tests', 'log' => $this->logDir, 'data' => $this->dataDir, - 'helpers' => $this->helperDir + 'support' => $this->supportDir ), 'settings' => array( 'bootstrap' => '_bootstrap.php', @@ -141,7 +143,7 @@ protected function createFunctionalSuite($actor = 'Functional') { $suiteConfig = array( 'class_name' => $actor.$this->actorSuffix, - 'modules' => array('enabled' => array('Filesystem', $actor.'Helper')), + 'modules' => array('enabled' => array('Filesystem', "\\{$this->namespace}Helper\\$actor")), ); $str = "# Codeception Test Suite Configuration\n\n"; @@ -157,7 +159,7 @@ protected function createAcceptanceSuite($actor = 'Acceptance') $suiteConfig = array( 'class_name' => $actor.$this->actorSuffix, 'modules' => array( - 'enabled' => array('PhpBrowser', $actor . 'Helper'), + 'enabled' => array('PhpBrowser', "\\{$this->namespace}Helper\\$actor"), 'config' => array( 'PhpBrowser' => array( 'url' => 'http://localhost/myapp/' @@ -179,7 +181,7 @@ protected function createUnitSuite($actor = 'Unit') { $suiteConfig = array( 'class_name' => $actor.$this->actorSuffix, - 'modules' => array('enabled' => array('Asserts', $actor . 'Helper')), + 'modules' => array('enabled' => array('Asserts', "\\{$this->namespace}Helper\\$actor")), ); $str = "# Codeception Test Suite Configuration\n\n"; @@ -196,9 +198,10 @@ protected function createSuite($suite, $actor, $config) "tests/$suite/_bootstrap.php", "supportDir.DIRECTORY_SEPARATOR."Helper"); file_put_contents( - $this->helperDir.DIRECTORY_SEPARATOR.$actor.'Helper.php', - (new Helper($actor, $this->namespace))->produce() + $this->supportDir.DIRECTORY_SEPARATOR."Helper".DIRECTORY_SEPARATOR."$actor.php", + (new Helper($actor, rtrim($this->namespace, '\\')))->produce() ); file_put_contents("tests/$suite.suite.yml", $config); } @@ -215,7 +218,7 @@ protected function createDirs() @mkdir('tests'); @mkdir($this->logDir); @mkdir($this->dataDir); - @mkdir($this->helperDir); + @mkdir($this->supportDir); file_put_contents($this->dataDir . '/dump.sql', '/* Replace this file with actual dump of your database */'); } diff --git a/src/Codeception/Command/Build.php b/src/Codeception/Command/Build.php index b21dd3e429..01d309ed8f 100644 --- a/src/Codeception/Command/Build.php +++ b/src/Codeception/Command/Build.php @@ -2,6 +2,7 @@ namespace Codeception\Command; use Codeception\Configuration; +use Codeception\Lib\Generator\Actions as ActionsGenerator; use Codeception\Lib\Generator\Actor as ActorGenerator; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; @@ -64,14 +65,23 @@ protected function buildActorsForConfig($configFile) } foreach ($suites as $suite) { $settings = $this->getSuiteConfig($suite, $configFile); - $gen = new ActorGenerator($settings); - $this->output->writeln(''.Configuration::config()['namespace'].'\\'.$gen->getActorName() . " includes modules: ".implode(', ',$gen->getModules())); - $contents = $gen->produce(); + $actionsGenerator = new ActionsGenerator($settings); + $contents = $actionsGenerator->produce(); - @mkdir($settings['path'],0755, true); - $file = $settings['path'].$this->getClassName($settings['class_name']).'.php'; + $actorGenerator = new ActorGenerator($settings); + $file = $this->buildPath(Configuration::supportDir().'_generated', $settings['class_name']).$this->getClassName($settings['class_name']).'Actions.php'; $this->save($file, $contents, true); - $this->output->writeln("{$settings['class_name']}.php generated successfully. ".$gen->getNumMethods()." methods added"); + + $this->output->writeln(''.Configuration::config()['namespace'].'\\'.$actorGenerator->getActorName() . " includes modules: ".implode(', ',$actorGenerator->getModules())); + $this->output->writeln(" -> {$settings['class_name']}Actions.php generated successfully. ".$actionsGenerator->getNumMethods()." methods added"); + + $contents = $actorGenerator->produce(); + + $file = $this->buildPath(Configuration::supportDir(), $settings['class_name']).$this->getClassName($settings['class_name']).'.php'; + if ($this->save($file, $contents)) { + $this->output->writeln("{$settings['class_name']}.php created."); + } + } } diff --git a/src/Codeception/Command/GenerateGroup.php b/src/Codeception/Command/GenerateGroup.php index 40dbe9dc13..31ce1574be 100644 --- a/src/Codeception/Command/GenerateGroup.php +++ b/src/Codeception/Command/GenerateGroup.php @@ -1,6 +1,7 @@ getArgument('group'); $class = ucfirst($group); - $path = $this->buildPath($config['paths']['tests'].'/_groups/', $class); - $filename = $this->completeSuffix($class, 'Group'); - $filename = $path.$filename; + $path = $this->buildPath(Configuration::supportDir().'Group'. DIRECTORY_SEPARATOR, $class); - $this->introduceAutoloader($config['paths']['tests'].DIRECTORY_SEPARATOR.$config['settings']['bootstrap'], $config['namespace'], '_groups'); + $filename = $path.$class.'.php'; $gen = new GroupGenerator($config, $group); $res = $this->save($filename, $gen->produce()); diff --git a/src/Codeception/Command/GenerateHelper.php b/src/Codeception/Command/GenerateHelper.php index c61bfd4935..eb3992836c 100644 --- a/src/Codeception/Command/GenerateHelper.php +++ b/src/Codeception/Command/GenerateHelper.php @@ -38,7 +38,7 @@ public function execute(InputInterface $input, OutputInterface $output) { $name = ucfirst($input->getArgument('name')); $config = \Codeception\Configuration::config($input->getOption('config')); - $file = \Codeception\Configuration::helpersDir() . "{$name}Helper.php"; + $file = \Codeception\Configuration::supportDir() . "Helper\\{$name}.php"; $res = $this->save($file, (new Helper($name, $config['namespace']))->produce()); if ($res) { diff --git a/src/Codeception/Command/GeneratePageObject.php b/src/Codeception/Command/GeneratePageObject.php index 796628e22f..a2705a2899 100644 --- a/src/Codeception/Command/GeneratePageObject.php +++ b/src/Codeception/Command/GeneratePageObject.php @@ -51,11 +51,10 @@ public function execute(InputInterface $input, OutputInterface $output) ? $this->getSuiteConfig($suite, $input->getOption('config')) : $this->getGlobalConfig($input->getOption('config')); + $class = ucfirst($suite) .'\\' . $class; $className = $this->getClassName($class); - $filename = $suite - ? $this->pathToSuitePageObject($conf, $className) - : $this->pathToGlobalPageObject($conf, $className); + $filename = $this->pathToPageObject($className, $suite); $gen = new PageObjectGenerator($conf, $class); $res = $this->save($filename, $gen->produce()); @@ -67,20 +66,13 @@ public function execute(InputInterface $input, OutputInterface $output) $output->writeln("PageObject was created in $filename"); } - protected function pathToGlobalPageObject($config, $class) + protected function pathToPageObject($class, $suite) { - $path = $this->buildPath(Configuration::projectDir().$config['paths']['tests'].'/_pages/', $class); - $filename = $this->completeSuffix($class, 'Page'); - $this->introduceAutoloader(Configuration::projectDir().$config['paths']['tests'].DIRECTORY_SEPARATOR.$config['settings']['bootstrap'], $config['namespace'], '_pages'); - return $path.$filename; - } - - protected function pathToSuitePageObject($config, $class) - { - $path = $this->buildPath($config['path'].'/_pages/', $class); - $filename = $this->completeSuffix($class, 'Page'); - $this->introduceAutoloader($config['path'].DIRECTORY_SEPARATOR.$config['bootstrap'], $config['namespace'], '_pages'); - return $path.$filename; + if ($suite) { + $suite = DIRECTORY_SEPARATOR . ucfirst($suite); + } + $path = $this->buildPath(Configuration::supportDir().'Page'.$suite, $class); + return $path.$class.'.php'; } } diff --git a/src/Codeception/Command/GenerateStepObject.php b/src/Codeception/Command/GenerateStepObject.php index 65f82e6c86..2e0bd747ec 100644 --- a/src/Codeception/Command/GenerateStepObject.php +++ b/src/Codeception/Command/GenerateStepObject.php @@ -1,12 +1,14 @@ getSuiteConfig($suite, $input->getOption('config')); $class = $this->getClassName($step); - $class = $this->removeSuffix($class, 'Steps'); - $path = $this->buildPath($config['path'].'/_steps/', $class); - $filename = $this->completeSuffix($class, 'Steps'); - $filename = $path.$filename; + $path = $this->buildPath(Configuration::supportDir().'Step'. DIRECTORY_SEPARATOR . ucfirst($suite), $class); + + $filename = $path.$class.'.php'; - $dialog = $this->getHelperSet()->get('dialog'); + $helper = $this->getHelper('question'); + $question = new Question("Add action to StepObject class (ENTER to exit): "); - $gen = new StepObjectGenerator($config, $class); + $gen = new StepObjectGenerator($config, ucfirst($suite) .'\\' . $class); if (!$input->getOption('silent')) { do { - $action = $dialog->ask($output, "Add action to StepObject class (ENTER to exit): ", null); + $action = $helper->ask($input, $output, $question); if ($action) { $gen->createAction($action); } @@ -62,13 +65,10 @@ public function execute(InputInterface $input, OutputInterface $output) $res = $this->save($filename, $gen->produce()); - $this->introduceAutoloader($config['path'].'/'.$config['bootstrap'], trim($config['namespace'].'\\'.$config['class_name'], '\\'), '_steps'); - if (!$res) { $output->writeln("StepObject $filename already exists"); exit; } $output->writeln("StepObject was created in $filename"); } - } diff --git a/src/Codeception/Configuration.php b/src/Codeception/Configuration.php index 545a644829..b6c19f7012 100644 --- a/src/Codeception/Configuration.php +++ b/src/Codeception/Configuration.php @@ -3,6 +3,7 @@ namespace Codeception; use Codeception\Exception\Configuration as ConfigurationException; +use Codeception\Lib\Di; use Codeception\Util\Autoload; use Symfony\Component\Yaml\Yaml; use Symfony\Component\Finder\Finder; @@ -36,7 +37,7 @@ class Configuration /** * @var string Directory containing helpers. Helpers will be autoloaded if they have suffix "Helper". */ - protected static $helpersDir = null; + protected static $supportDir = null; /** * @var string Directory containing tests and suites of the current project. @@ -144,12 +145,17 @@ public static function config($configFile = null) throw new ConfigurationException('Data path is not defined Codeception config by key "paths: data"'); } - if (!isset($config['paths']['helpers'])) { - throw new ConfigurationException('Helpers path is not defined by key "paths: helpers"'); + // compatibility with 1.x, 2.0 + if (!isset($config['paths']['support']) and isset($config['paths']['helpers'])) { + $config['paths']['support'] = $config['paths']['helpers']; + } + + if (!isset($config['paths']['support'])) { + throw new ConfigurationException('Helpers path is not defined by key "paths: support"'); } self::$dataDir = $config['paths']['data']; - self::$helpersDir = $config['paths']['helpers']; + self::$supportDir = $config['paths']['support']; self::$testsDir = $config['paths']['tests']; self::loadBootstrap($config['settings']['bootstrap']); @@ -179,8 +185,10 @@ protected static function loadConfigFile($file, $parentConfig) protected static function autoloadHelpers() { $namespace = self::$config['namespace']; - Autoload::addNamespace($namespace, self::helpersDir()); - Autoload::addNamespace($namespace.'\Codeception\Module', self::helpersDir()); + Autoload::addNamespace($namespace, self::supportDir()); + + // deprecated + Autoload::addNamespace($namespace.'\Codeception\Module', self::supportDir()); } protected static function loadSuites() @@ -286,7 +294,7 @@ public static function modules($settings) foreach ($moduleNames as $moduleName) { $moduleConfig = (isset($settings['modules']['config'][$moduleName])) ? $settings['modules']['config'][$moduleName] : array(); - $modules[$moduleName] = static::createModule($moduleName, $moduleConfig, $namespace); + $modules[$moduleName] = static::createModule($moduleName, [$moduleConfig], $namespace); } return $modules; @@ -296,10 +304,8 @@ public static function modules($settings) * Creates new module and configures it. * Module class is searched and resolves according following rules: * - * 1. if "class" element is fully qualified class name, it will be taken to create module; - * 2. module class will be searched under default namespace, according $namespace parameter: - * $namespace.'\Codeception\Module\' . $class; - * 3. module class will be searched under Codeception module namespace, that is "\Codeception\Module". + * 1. if "class" element is fully qualified class name (started with "\"), it will be taken to create module; + * 2. module class will be searched under Codeception module namespace, that is "\Codeception\Module". * * @param $class * @param array $config module configuration @@ -309,24 +315,25 @@ public static function modules($settings) */ public static function createModule($class, $config, $namespace = '') { + $di = new Di(); $hasNamespace = (mb_strpos($class, '\\') !== false); if ($hasNamespace) { - return new $class($config); + return $di->instantiate($class, $config); } // try find module under users suite namespace setting $className = $namespace.'\\Codeception\\Module\\' . $class; - if (!@class_exists($className)) { + if (!class_exists($className)) { // fallback to default namespace $className = '\\Codeception\\Module\\' . $class; - if (!@class_exists($className)) { + if (!class_exists($className)) { throw new ConfigurationException($class.' could not be found and loaded'); } } - return new $className($config); + return $di->instantiate($className, $config); } public static function isExtensionEnabled($extensionName) @@ -386,9 +393,9 @@ public static function dataDir() * * @return string */ - public static function helpersDir() + public static function supportDir() { - return self::$dir . DIRECTORY_SEPARATOR . self::$helpersDir . DIRECTORY_SEPARATOR; + return self::$dir . DIRECTORY_SEPARATOR . self::$supportDir . DIRECTORY_SEPARATOR; } /** diff --git a/src/Codeception/Exception/InjectionException.php b/src/Codeception/Exception/InjectionException.php new file mode 100644 index 0000000000..be3a1d773b --- /dev/null +++ b/src/Codeception/Exception/InjectionException.php @@ -0,0 +1,7 @@ +container[$className])) { + if ($this->container[$className] instanceof $className) { + return $this->container[$className]; + } else { + throw new InjectionException("Failed to resolve cyclic dependencies for class '$className'"); + } + } + $this->container[$className] = false; // flag that object is being instantiated + + $reflectedClass = new \ReflectionClass($className); + if (!$reflectedClass->isInstantiable()) { + return null; + } + + $reflectedConstructor = $reflectedClass->getConstructor(); + if (is_null($reflectedConstructor)) { + $object = new $className; + } else { + try { + if (!$constructorArgs) { + $constructorArgs = $this->prepareArgs($reflectedConstructor); + } + } catch (\Exception $e) { + throw new InjectionException("Failed to create instance of '$className'. ".$e->getMessage()); + } + $object = $reflectedClass->newInstanceArgs($constructorArgs); + } + + $this->injectDependencies($object, $injectMethodName); + + $this->container[$className] = $object; + return $object; + } + + /** + * @param $object + * @param string $injectMethodName Method which will be invoked with resolved dependencies as its arguments + * @throws InjectionException + */ + public function injectDependencies($object, $injectMethodName = self::DEFAULT_INJECT_METHOD_NAME) + { + if (!is_object($object)) { + return; + } + + $reflectedObject = new \ReflectionObject($object); + if (!$reflectedObject->hasMethod($injectMethodName)) { + return; + } + + $reflectedMethod = $reflectedObject->getMethod($injectMethodName); + try { + $args = $this->prepareArgs($reflectedMethod); + } catch (\Exception $e) { + throw new InjectionException("Failed to inject dependencies in instance of '{$reflectedObject->name}'. ".$e->getMessage()); + } + + if (!$reflectedMethod->isPublic()) { + $reflectedMethod->setAccessible(true); + } + $reflectedMethod->invokeArgs($object, $args); + } + + /** + * @param \ReflectionMethod $method + * @return array + * @throws \Exception + */ + protected function prepareArgs(\ReflectionMethod $method) + { + $args = []; + $parameters = $method->getParameters(); + foreach ($parameters as $parameter) { + $dependency = $parameter->getClass(); + if (is_null($dependency)) { + if (!$parameter->isOptional()) { + throw new InjectionException("Parameter '$parameter->name' must have default value."); + } + $args[] = $parameter->getDefaultValue(); + } else { + $arg = $this->instantiate($dependency->name); + if (is_null($arg)) { + throw new InjectionException("Failed to resolve dependency '{$dependency->name}'."); + } + $args[] = $arg; + } + } + return $args; + } +} diff --git a/src/Codeception/Lib/Generator/Actions.php b/src/Codeception/Lib/Generator/Actions.php new file mode 100644 index 0000000000..34c4b06bcc --- /dev/null +++ b/src/Codeception/Lib/Generator/Actions.php @@ -0,0 +1,200 @@ +scenario->runStep(new \Codeception\Step\{{step}}('{{method}}', func_get_args())); + } +EOF; + + protected $name; + protected $settings; + protected $modules; + protected $actions; + protected $numMethods = 0; + + public function __construct($settings) + { + $this->name = $settings['class_name']; + $this->settings = $settings; + $this->modules = \Codeception\Configuration::modules($this->settings); + $this->actions = \Codeception\Configuration::actions($this->modules); + } + + + public function produce() + { + $namespace = rtrim($this->settings['namespace'], '\\'); + + $uses = []; + foreach ($this->modules as $module) { + $uses[] = "use " . get_class($module) . ";"; + } + + $methods = []; + $code = []; + foreach ($this->actions as $action => $moduleName) { + if (in_array($action, $methods)) { + continue; + } + $class = new \ReflectionClass($this->modules[$moduleName]); + $method = $class->getMethod($action); + $code[] = $this->addMethod($method); + $methods[] = $action; + $this->numMethods++; + } + + return (new Template($this->template)) + ->place('namespace', $namespace ? $namespace . '\\' : '') + ->place('hash', self::genHash($this->actions, $this->settings)) + ->place('name', $this->name) + ->place('use', implode("\n", $uses)) + ->place('methods', implode("\n\n ", $code)) + ->produce(); + } + + protected function addMethod(\ReflectionMethod $refMethod) + { + $class = $refMethod->getDeclaringClass(); + $params = $this->getParamsString($refMethod); + $module = $class->getName(); + + $body = ''; + $doc = $this->addDoc($class, $refMethod); + $doc = str_replace('/**', '', $doc); + $doc = trim(str_replace('*/', '', $doc)); + if (!$doc) { + $doc = "*"; + } + + $conditionalDoc = $doc . "\n * Conditional Assertion: Test won't be stopped on fail"; + + $methodTemplate = (new Template($this->methodTemplate)) + ->place('module', $module) + ->place('method', $refMethod->name) + ->place('params', $params); + + // generate conditional assertions + if (0 === strpos($refMethod->name, 'see')) { + $type = 'Assertion'; + $body .= $methodTemplate + ->place('doc', $conditionalDoc) + ->place('action', 'can' . ucfirst($refMethod->name)) + ->place('step', 'ConditionalAssertion') + ->produce(); + + // generate negative assertion + } elseif (0 === strpos($refMethod->name, 'dontSee')) { + $type = 'Assertion'; + $body .= $methodTemplate + ->place('doc', $conditionalDoc) + ->place('action', str_replace('dont', 'cant', $refMethod->name)) + ->place('step', 'ConditionalAssertion') + ->produce(); + + } elseif (0 === strpos($refMethod->name, 'am')) { + $type = 'Condition'; + } else { + $type = 'Action'; + } + + $body .= $methodTemplate + ->place('doc', $doc) + ->place('action', $refMethod->name) + ->place('step', $type) + ->produce(); + + return $body; + } + + /** + * @param \ReflectionMethod $refMethod + * @return array + */ + protected function getParamsString(\ReflectionMethod $refMethod) + { + $params = array(); + foreach ($refMethod->getParameters() as $param) { + + if ($param->isOptional()) { + $params[] = '$' . $param->name . ' = null'; + } else { + $params[] = '$' . $param->name; + }; + + } + return implode(', ', $params); + } + + /** + * @param \ReflectionClass $class + * @param \ReflectionMethod $refMethod + * @return string + */ + protected function addDoc(\ReflectionClass $class, \ReflectionMethod $refMethod) + { + $doc = $refMethod->getDocComment(); + + if (!$doc) { + $interfaces = $class->getInterfaces(); + foreach ($interfaces as $interface) { + $i = new \ReflectionClass($interface->name); + if ($i->hasMethod($refMethod->name)) { + $doc = $i->getMethod($refMethod->name)->getDocComment(); + break; + } + } + } + + if (!$doc and $class->getParentClass()) { + $parent = new \ReflectionClass($class->getParentClass()->name); + if ($parent->hasMethod($refMethod->name)) { + $doc = $parent->getMethod($refMethod->name)->getDocComment(); + return $doc; + } + return $doc; + } + return $doc; + } + + public static function genHash($actions, $settings) + { + return md5(Codecept::VERSION.serialize($actions).serialize($settings['modules'])); + } + + public function getNumMethods() + { + return $this->numMethods; + } + + +} \ No newline at end of file diff --git a/src/Codeception/Lib/Generator/Actor.php b/src/Codeception/Lib/Generator/Actor.php index 5c16a119a9..90595069a2 100644 --- a/src/Codeception/Lib/Generator/Actor.php +++ b/src/Codeception/Lib/Generator/Actor.php @@ -1,20 +1,13 @@ scenario->runStep(new \Codeception\Step\{{step}}('{{method}}', func_get_args())); - } EOF; protected $inheritedMethodTemplate = ' * @method void {{method}}({{params}})'; @@ -47,7 +32,6 @@ public function {{action}}({{params}}) { protected $settings; protected $modules; protected $actions; - protected $numMethods = 0; public function __construct($settings) { @@ -60,91 +44,35 @@ public function produce() { $namespace = rtrim($this->settings['namespace'], '\\'); - $uses = []; - foreach ($this->modules as $module) { - $uses[] = "use " . get_class($module) . ";"; - } - - $methods = []; - $code = []; - foreach ($this->actions as $action => $moduleName) { - if (in_array($action, $methods)) { - continue; - } - $class = new \ReflectionClass($this->modules[$moduleName]); - $method = $class->getMethod($action); - $code[] = $this->addMethod($method); - $methods[] = $action; - $this->numMethods++; - } - return (new Template($this->template)) - ->place('namespace', $namespace ? "namespace $namespace;" : '') - ->place('hash', self::genHash($this->actions, $this->settings)) - ->place('use', implode("\n", $uses)) + ->place('hasNamespace', $namespace ? "namespace $namespace;" : '') ->place('actor', $this->settings['class_name']) - ->place('methods', implode("\n\n ", $code)) ->place('inheritedMethods', $this->prependAbstractGuyDocBlocks()) ->produce(); } - public static function genHash($actions, $settings) - { - return md5(Codecept::VERSION.serialize($actions).serialize($settings['modules'])); - } - - protected function addMethod(\ReflectionMethod $refMethod) + protected function prependAbstractGuyDocBlocks() { - $class = $refMethod->getDeclaringClass(); - $params = $this->getParamsString($refMethod); - $module = $class->getName(); - - $body = ''; - $doc = $this->addDoc($class, $refMethod); - $doc = str_replace('/**', '', $doc); - $doc = trim(str_replace('*/', '', $doc)); - if (!$doc) { - $doc = "*"; - } - - $conditionalDoc = $doc . "\n * Conditional Assertion: Test won't be stopped on fail"; - - $methodTemplate = (new Template($this->methodTemplate)) - ->place('module', $module) - ->place('method', $refMethod->name) - ->place('params', $params); + $inherited = array(); - // generate conditional assertions - if (0 === strpos($refMethod->name, 'see')) { - $type = 'Assertion'; - $body .= $methodTemplate - ->place('doc', $conditionalDoc) - ->place('action', 'can' . ucfirst($refMethod->name)) - ->place('step', 'ConditionalAssertion') - ->produce(); + $class = new \ReflectionClass('\Codeception\\Actor'); + $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); - // generate negative assertion - } elseif (0 === strpos($refMethod->name, 'dontSee')) { - $type = 'Assertion'; - $body .= $methodTemplate - ->place('doc', $conditionalDoc) - ->place('action', str_replace('dont', 'cant', $refMethod->name)) - ->place('step', 'ConditionalAssertion') + foreach ($methods as $method) { + if ($method->name == '__call') { + continue; + } // skipping magic + if ($method->name == '__construct') { + continue; + } // skipping magic + $params = $this->getParamsString($method); + $inherited[] = (new Template($this->inheritedMethodTemplate)) + ->place('method', $method->name) + ->place('params', $params) ->produce(); - - } elseif (0 === strpos($refMethod->name, 'am')) { - $type = 'Condition'; - } else { - $type = 'Action'; } - $body .= $methodTemplate - ->place('doc', $doc) - ->place('action', $refMethod->name) - ->place('step', $type) - ->produce(); - - return $body; + return implode("\n", $inherited); } /** @@ -166,61 +94,6 @@ protected function getParamsString(\ReflectionMethod $refMethod) return implode(', ', $params); } - /** - * @param \ReflectionClass $class - * @param \ReflectionMethod $refMethod - * @return string - */ - protected function addDoc(\ReflectionClass $class, \ReflectionMethod $refMethod) - { - $doc = $refMethod->getDocComment(); - - if (!$doc) { - $interfaces = $class->getInterfaces(); - foreach ($interfaces as $interface) { - $i = new \ReflectionClass($interface->name); - if ($i->hasMethod($refMethod->name)) { - $doc = $i->getMethod($refMethod->name)->getDocComment(); - break; - } - } - } - - if (!$doc and $class->getParentClass()) { - $parent = new \ReflectionClass($class->getParentClass()->name); - if ($parent->hasMethod($refMethod->name)) { - $doc = $parent->getMethod($refMethod->name)->getDocComment(); - return $doc; - } - return $doc; - } - return $doc; - } - - protected function prependAbstractGuyDocBlocks() - { - $inherited = array(); - - $class = new \ReflectionClass('\Codeception\\Actor'); - $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); - - foreach ($methods as $method) { - if ($method->name == '__call') { - continue; - } // skipping magic - if ($method->name == '__construct') { - continue; - } // skipping magic - $params = $this->getParamsString($method); - $inherited[] = (new Template($this->inheritedMethodTemplate)) - ->place('method', $method->name) - ->place('params', $params) - ->produce(); - } - - return implode("\n", $inherited); - } - public function getActorName() { return $this->settings['class_name']; @@ -231,10 +104,4 @@ public function getModules() return array_keys($this->modules); } - public function getNumMethods() - { - return $this->numMethods; - } - - } diff --git a/src/Codeception/Lib/Generator/Group.php b/src/Codeception/Lib/Generator/Group.php index 646f7581e4..34d2f50e19 100644 --- a/src/Codeception/Lib/Generator/Group.php +++ b/src/Codeception/Lib/Generator/Group.php @@ -10,7 +10,7 @@ class Group { protected $template = <<settings = $settings; - $this->name = $this->removeSuffix($name, 'Group'); + $this->name = $name; + $this->namespace = $this->getNamespaceString($this->settings['namespace'].'\\Group\\'.$name); + } public function produce() { - $ns = $this->getNamespaceString($this->settings['namespace'].'\\'.$this->name); return (new Template($this->template)) ->place('class', ucfirst($this->name)) ->place('name', $this->name) - ->place('namespace', $ns) + ->place('namespace', $this->namespace) + ->place('groupName', strtolower($this->name)) ->produce(); } diff --git a/src/Codeception/Lib/Generator/Helper.php b/src/Codeception/Lib/Generator/Helper.php index 153c60a1b7..53442dd864 100644 --- a/src/Codeception/Lib/Generator/Helper.php +++ b/src/Codeception/Lib/Generator/Helper.php @@ -3,16 +3,16 @@ use Codeception\Util\Template; -class Helper { +class Helper +{ protected $template = <<namespace = $namespace - ? $namespace . '\\' - : ''; + $this->namespace = $namespace ? "$namespace\\" : $namespace; $this->name = $name; } diff --git a/src/Codeception/Lib/Generator/PageObject.php b/src/Codeception/Lib/Generator/PageObject.php index 904ae643ce..60235cc874 100644 --- a/src/Codeception/Lib/Generator/PageObject.php +++ b/src/Codeception/Lib/Generator/PageObject.php @@ -10,8 +10,9 @@ class PageObject protected $template = <<settings = $settings; - $this->name = $this->getShortClassName($this->removeSuffix($name, 'Page')); + $this->name = $this->getShortClassName($name); + $this->namespace = $this->getNamespaceString($this->settings['namespace'].'\\'.$name); } public function produce() { - $ns = $this->getNamespaceString($this->settings['namespace'].'\\'.$this->name); - return (new Template($this->template)) - ->place('namespace', $ns) + ->place('namespace', $this->namespace) ->place('actions', $this->produceActions()) ->place('class', $this->name) ->produce(); diff --git a/src/Codeception/Lib/Generator/Shared/Namespaces.php b/src/Codeception/Lib/Generator/Shared/Namespaces.php index e7e98d45ac..4f2941725c 100644 --- a/src/Codeception/Lib/Generator/Shared/Namespaces.php +++ b/src/Codeception/Lib/Generator/Shared/Namespaces.php @@ -13,15 +13,14 @@ protected function getShortClassName($class) protected function getNamespaceString($class) { $namespaces = $this->getNamespaces($class); - return $namespaces - ? 'namespace ' . implode('\\', $namespaces) . ";\n" - : ''; + return implode('\\', $namespaces); } protected function getNamespaces($class) { $namespaces = $this->breakParts($class); array_pop($namespaces); + $namespaces = array_filter($namespaces, 'strlen'); return $namespaces; } diff --git a/src/Codeception/Lib/Generator/StepObject.php b/src/Codeception/Lib/Generator/StepObject.php index 4fcd481a67..6ccf1891c3 100644 --- a/src/Codeception/Lib/Generator/StepObject.php +++ b/src/Codeception/Lib/Generator/StepObject.php @@ -3,24 +3,28 @@ use Codeception\Util\Template; -class StepObject { +class StepObject +{ use Shared\Namespaces; use Shared\Classname; protected $template = <<settings = $settings; - $this->name = $this->removeSuffix($name, 'Steps'); + $this->name = $this->getShortClassName($name); + $this->namespace = $this->getNamespaceString($this->settings['namespace'].'\\Step\\'.$name); } public function produce() { $actor = $this->settings['class_name']; - $ns = $this->getNamespaceString($this->settings['namespace'].'\\'.$actor.'\\'.$this->name); - $ns = ltrim($ns, '\\'); - $extended = '\\'.ltrim('\\'.$this->settings['namespace'].'\\'.$actor, '\\'); return (new Template($this->template)) - ->place('namespace', $ns) + ->place('namespace', $this->namespace) ->place('name', $this->name) ->place('actorClass', $extended) ->place('actions', $this->actions) diff --git a/src/Codeception/Subscriber/AutoRebuild.php b/src/Codeception/Subscriber/AutoRebuild.php index 83ae956d77..d030ad1ae8 100644 --- a/src/Codeception/Subscriber/AutoRebuild.php +++ b/src/Codeception/Subscriber/AutoRebuild.php @@ -3,7 +3,7 @@ use Codeception\Events; use Codeception\Event\SuiteEvent; -use Codeception\Lib\Generator\Actor; +use Codeception\Lib\Generator\Actions; use Codeception\SuiteManager; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -12,29 +12,29 @@ class AutoRebuild implements EventSubscriberInterface use Shared\StaticEvents; static $events = [ - Events::SUITE_INIT => 'updateGuy' + Events::SUITE_INIT => 'updateActor' ]; - public function updateGuy(SuiteEvent $e) + public function updateActor(SuiteEvent $e) { $settings = $e->getSettings(); - $guyFile = $settings['path'] . $settings['class_name'] . '.php'; + $actorFile = $settings['path'] . DIRECTORY_SEPARATOR . '_generated' . DIRECTORY_SEPARATOR . $settings['class_name'] . 'Actions.php'; - // load guy class to see hash - $handle = fopen($guyFile, "r"); + // load actor class to see hash + $handle = @fopen($actorFile, "r"); if ($handle) { - $line = fgets($handle); + $line = @fgets($handle); if (preg_match('~\[STAMP\] ([a-f0-9]*)~', $line, $matches)) { $hash = $matches[1]; - $currentHash = Actor::genHash(SuiteManager::$actions, $settings); + $currentHash = Actions::genHash(SuiteManager::$actions, $settings); // regenerate guy class when hashes do not match if ($hash != $currentHash) { codecept_debug("Rebuilding {$settings['class_name']}..."); - $guyGenerator = new Actor($settings); - fclose($handle); - $generated = $guyGenerator->produce(); - file_put_contents($guyFile, $generated); + $actionsGenerator = new Actions($settings); + @fclose($handle); + $generated = $actionsGenerator->produce(); + @file_put_contents($actorFile, $generated); return; } } diff --git a/src/Codeception/SuiteManager.php b/src/Codeception/SuiteManager.php index aa09a0af98..e24d54c054 100644 --- a/src/Codeception/SuiteManager.php +++ b/src/Codeception/SuiteManager.php @@ -75,10 +75,9 @@ protected function initializeModules() protected function initializeActors() { - if (!file_exists($this->path . $this->settings['class_name'] . '.php')) { + if (!file_exists(Configuration::supportDir() . $this->settings['class_name'] . '.php')) { throw new Exception\Configuration($this->settings['class_name'] . " class doesn't exists in suite folder.\nRun the 'build' command to generate it"); } - require_once $this->settings['path'] . DIRECTORY_SEPARATOR . $this->settings['class_name'] . '.php'; } public static function hasModule($moduleName) diff --git a/src/Codeception/TestLoader.php b/src/Codeception/TestLoader.php index 39625c0bcd..9998d44a7a 100644 --- a/src/Codeception/TestLoader.php +++ b/src/Codeception/TestLoader.php @@ -2,6 +2,7 @@ namespace Codeception; use Codeception\Lib\Parser; +use Codeception\Lib\Di; use Codeception\TestCase\Cept; use Codeception\TestCase\Cest; use Codeception\Util\Annotation; @@ -47,6 +48,7 @@ class TestLoader { public function __construct($path) { $this->path = $path; + $this->di = new Di; } public function getTests() @@ -54,6 +56,7 @@ public function getTests() return $this->tests; } + protected function relativeName($file) { return $name = str_replace([$this->path, '\\'], ['', '/'], $file); @@ -157,12 +160,11 @@ public function addCest($file) $testClasses = Parser::getClassesFromFile($file); foreach ($testClasses as $testClass) { - $reflected = new \ReflectionClass($testClass); - if ($reflected->isAbstract()) { + $unit = $this->di->instantiate($testClass); + if (!$unit) { continue; } - $unit = new $testClass; $methods = get_class_methods($testClass); foreach ($methods as $method) { $test = $this->createTestFromCestMethod($unit, $method, $file); @@ -204,6 +206,7 @@ protected function enhancePhpunitTest(\PHPUnit_Framework_TestCase $test) } $test->initConfig(); $test->getScenario()->env(Annotation::forMethod($className, $methodName)->fetchAll('env')); + $this->di->injectDependencies($test); } protected function createTestFromCestMethod($cestInstance, $methodName, $file) @@ -225,5 +228,4 @@ protected function createTestFromCestMethod($cestInstance, $methodName, $file) return $cest; } - } diff --git a/tests/cli/RunCest.php b/tests/cli/RunCest.php index f92f34d413..ce0d0d1d50 100644 --- a/tests/cli/RunCest.php +++ b/tests/cli/RunCest.php @@ -131,7 +131,7 @@ public function runTwoSuites(\CliGuy $I) public function skipSuites(\CliGuy $I) { $I->executeCommand( - 'run --skip skipped --skip remote --skip remote_server --skip order --skip unit --skip powers' + 'run --skip skipped --skip remote --skip remote_server --skip order --skip unit --skip powers --skip math' ); $I->seeInShellOutput("Dummy Tests"); $I->dontSeeInShellOutput("Remote Tests"); @@ -184,7 +184,18 @@ public function runWithCustomOuptutPath(\CliGuy $I) $I->seeFileFound('myownhtmlreport.html', 'tests/_log'); $I->dontSeeFileFound('report.xml','tests/_log'); $I->dontSeeFileFound('report.html','tests/_log'); + } + public function runTestsWithDependencyInjections(\CliGuy $I) + { + $I->executeCommand('run math'); + $I->seeInShellOutput('Trying to test addition (MathCest::testAddition)'); + $I->seeInShellOutput('Trying to test subtraction (MathCest::testSubtraction)'); + $I->seeInShellOutput('Trying to test square (MathCest::testSquare)'); + $I->seeInShellOutput('Trying to test all (MathTest::testAll)'); + $I->seeInShellOutput('OK ('); + $I->dontSeeInShellOutput('fail'); + $I->dontSeeInShellOutput('error'); } public function runErrorTest(\CliGuy $I) diff --git a/tests/data/FailDependenciesCyclicCest.php b/tests/data/FailDependenciesCyclicCest.php new file mode 100644 index 0000000000..f416b73ace --- /dev/null +++ b/tests/data/FailDependenciesCyclicCest.php @@ -0,0 +1,12 @@ +pi = $pi; + } + + protected function _inject(Adder $adder, Subtractor $subtractor) + { + $this->adder = $adder; + $this->subtractor = $subtractor; + } + + public function add($a, $b) + { + return $this->adder->perform($a, $b); + } + + public function subtract($a, $b) + { + return $this->subtractor->perform($a, $b); + } + + public function squareOfCircle($radius) + { + return $this->pi * pow($radius, 2); + } +} diff --git a/tests/data/claypit/tests/_helpers/SubtractorHelper.php b/tests/data/claypit/tests/_helpers/SubtractorHelper.php new file mode 100644 index 0000000000..476fbb49c4 --- /dev/null +++ b/tests/data/claypit/tests/_helpers/SubtractorHelper.php @@ -0,0 +1,11 @@ +calc = $calc; + } + + public function testAddition(MathTester $I) + { + $I->assertEquals(3, $this->calc->add(1, 2)); + $I->assertEquals(0, $this->calc->add(10, -10)); + } + + public function testSubtraction(MathTester $I) + { + $I->assertEquals(1, $this->calc->subtract(3, 2)); + $I->assertEquals(0, $this->calc->subtract(5, 5)); + } + + public function testSquare(MathTester $I) + { + $I->assertEquals(3, $this->calc->squareOfCircle(1)); + $I->assertEquals(12, $this->calc->squareOfCircle(2)); + } +} diff --git a/tests/data/claypit/tests/math/MathTest.php b/tests/data/claypit/tests/math/MathTest.php new file mode 100644 index 0000000000..a3465d9340 --- /dev/null +++ b/tests/data/claypit/tests/math/MathTest.php @@ -0,0 +1,28 @@ +calc = $calc; + } + + public function testAll() + { + $this->assertEquals(3, $this->calc->add(1, 2)); + $this->assertEquals(1, $this->calc->subtract(3, 2)); + $this->assertEquals(75, $this->calc->squareOfCircle(5)); + } +} diff --git a/tests/data/claypit/tests/math/MathTester.php b/tests/data/claypit/tests/math/MathTester.php new file mode 100644 index 0000000000..baebf2e447 --- /dev/null +++ b/tests/data/claypit/tests/math/MathTester.php @@ -0,0 +1,213 @@ +scenario->runStep(new \Codeception\Step\Action('assertEquals', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that two variables are not equal + * + * @param $expected + * @param $actual + * @param string $message + * @see \Codeception\Module\Asserts::assertNotEquals() + */ + public function assertNotEquals($expected, $actual, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that expected is greater then actual + * + * @param $expected + * @param $actual + * @param string $message + * @see \Codeception\Module\Asserts::assertGreaterThen() + */ + public function assertGreaterThen($expected, $actual, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThen', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that expected is greater or equal then actual + * + * @param $expected + * @param $actual + * @param string $message + * @see \Codeception\Module\Asserts::assertGreaterThenOrEqual() + */ + public function assertGreaterThenOrEqual($expected, $actual, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThenOrEqual', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that haystack contains needle + * + * @param $needle + * @param $haystack + * @param string $message + * @see \Codeception\Module\Asserts::assertContains() + */ + public function assertContains($needle, $haystack, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertContains', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that haystack doesn't contain needle. + * + * @param $needle + * @param $haystack + * @param string $message + * @see \Codeception\Module\Asserts::assertNotContains() + */ + public function assertNotContains($needle, $haystack, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertNotContains', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that variable is empty. + * + * @param $actual + * @param string $message + * @see \Codeception\Module\Asserts::assertEmpty() + */ + public function assertEmpty($actual, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertEmpty', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that variable is not empty. + * + * @param $actual + * @param string $message + * @see \Codeception\Module\Asserts::assertNotEmpty() + */ + public function assertNotEmpty($actual, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertNotEmpty', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that variable is NULL + * + * @param $actual + * @param string $message + * @see \Codeception\Module\Asserts::assertNull() + */ + public function assertNull($actual, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertNull', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that variable is not NULL + * + * @param $actual + * @param string $message + * @see \Codeception\Module\Asserts::assertNotNull() + */ + public function assertNotNull($actual, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertNotNull', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that condition is positive. + * + * @param $condition + * @param string $message + * @see \Codeception\Module\Asserts::assertTrue() + */ + public function assertTrue($condition, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertTrue', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that condition is negative. + * + * @param $condition + * @param string $message + * @see \Codeception\Module\Asserts::assertFalse() + */ + public function assertFalse($condition, $message = null) { + return $this->scenario->runStep(new \Codeception\Step\Action('assertFalse', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Fails the test with message. + * + * @param $message + * @see \Codeception\Module\Asserts::fail() + */ + public function fail($message) { + return $this->scenario->runStep(new \Codeception\Step\Action('fail', func_get_args())); + } +} diff --git a/tests/data/claypit/tests/math/_bootstrap.php b/tests/data/claypit/tests/math/_bootstrap.php new file mode 100644 index 0000000000..7dfa7c30f2 --- /dev/null +++ b/tests/data/claypit/tests/math/_bootstrap.php @@ -0,0 +1,2 @@ +assertContains('Suite shire generated', $this->output); $helper = $this->log[1]; - $this->assertEquals(\Codeception\Configuration::helpersDir().'HobbitHelper.php',$helper['filename']); + $this->assertEquals(\Codeception\Configuration::supportDir().'HobbitHelper.php',$helper['filename']); $this->assertContains('class HobbitHelper extends \Codeception\Module', $helper['content']); $bootstrap = $this->log[0]; @@ -33,7 +33,7 @@ public function testGuyWithSuffix() $this->assertContains('HobbitHelper',$conf['modules']['enabled']); $helper = $this->log[1]; - $this->assertEquals(\Codeception\Configuration::helpersDir().'HobbitHelper.php',$helper['filename']); + $this->assertEquals(\Codeception\Configuration::supportDir().'HobbitHelper.php',$helper['filename']); $this->assertContains('class HobbitHelper extends \Codeception\Module', $helper['content']); } diff --git a/tests/unit/Codeception/TestLoaderTest.php b/tests/unit/Codeception/TestLoaderTest.php index 3fa8a12d02..c45e292972 100644 --- a/tests/unit/Codeception/TestLoaderTest.php +++ b/tests/unit/Codeception/TestLoaderTest.php @@ -52,24 +52,69 @@ public function testLoadFileWithFewCases() */ public function testLoadAllTests() { + Codeception\Util\Autoload::register('Math', 'Helper', codecept_data_dir().'claypit/tests/_helpers'); // to autoload dependencies + $this->testLoader = new \Codeception\TestLoader(codecept_data_dir().'claypit/tests'); $this->testLoader->loadTests(); - $this->assertContainsTestName('order/AnotherCept', $this->testLoader->getTests()); - $this->assertContainsTestName('MageGuildCest::darkPower', $this->testLoader->getTests()); - $this->assertContainsTestName('FailingTest::testMe', $this->testLoader->getTests()); + + $testNames = $this->getTestNames($this->testLoader->getTests()); + + $this->assertContainsTestName('order/AnotherCept', $testNames); + $this->assertContainsTestName('MageGuildCest::darkPower', $testNames); + $this->assertContainsTestName('FailingTest::testMe', $testNames); + $this->assertContainsTestName('MathCest::testAddition', $testNames); + $this->assertContainsTestName('MathTest::testAll', $testNames); } - protected function assertContainsTestName($name, $tests) + protected function getTestNames($tests) { + $testNames = []; foreach ($tests as $test) { if ($test instanceof \PHPUnit_Framework_TestCase) { - $testName = \Codeception\TestCase::getTestSignature($test); - if ($testName == $name) return; - codecept_debug($testName); + $testNames[] = \Codeception\TestCase::getTestSignature($test); } } - $this->fail("$name not found in tests"); + return $testNames; + } + + protected function assertContainsTestName($name, $testNames) + { + $this->assertNotSame(false, array_search($name, $testNames), "$name not found in tests"); + } + + public function testDependencyResolution() + { + $this->testLoader->loadTest('SimpleWithDependencyInjectionCest.php'); + $this->assertEquals(3, count($this->testLoader->getTests())); + } + + protected function shouldFail($msg = '') + { + $this->setExpectedException('Exception', $msg); + } + + public function testFailDependenciesCyclic() + { + $this->shouldFail('Failed to resolve cyclic dependencies for class \'FailDependenciesCyclic\IncorrectDependenciesClass\''); + $this->testLoader->loadTest('FailDependenciesCyclicCest.php'); + } + + public function testFailDependenciesInChain() + { + $this->shouldFail('Failed to resolve dependency \'FailDependenciesInChain\AnotherClass\''); + $this->testLoader->loadTest('FailDependenciesInChainCest.php'); + } + + public function testFailDependenciesNonExistent() + { + $this->shouldFail('Class FailDependenciesNonExistent\NonExistentClass does not exist'); + $this->testLoader->loadTest('FailDependenciesNonExistentCest.php'); + } + public function testFailDependenciesPrimitiveParam() + { + $this->shouldFail('Parameter \'required\' must have default value'); + $this->testLoader->loadTest('FailDependenciesPrimitiveParamCest.php'); } -} \ No newline at end of file +}