Codeception autoloading classes - php

I'm having issues using the Codeception autoloader to load some abstract test classes. The reason for the abstract class is to mimic the structure of classes that are used in the application to cut down on the amount of code needed to test the application thoroughly.
Lets say I have an abstract class for testing, let say "AbstractRepositoryTester" which is only used in the "repositories" test suite (I like to separate things out for organisational purposes).
Each and every repository that I test that implements a "RepositoryContract" will have a test that also extends the "AbstractRepositoryTester" with some overridden abstract methods.
Now when doing this, the abstract class won't be loaded during testing and has to be loaded manually in a bootstrap file. There is also another abstraction that extends the vanilla Codeception test class so that I can set a few variables (namely for laracasts/testdummy).
Both classes will fail to load with out manual entry in to the _boostrap file. To add to this, the suite specific bootstrap file fails to load files or seemingly execute at all so I am forced to place all bootstrap code for all suites in to the global _bootstrap file.
I also attempted to use Codeceptions autoloading class \Codeception\Util\Autoload:: with the "load" method but it doesn't seem to work.
Right now I'm using require_once in the global _bootstrap so finally to the question:
Is there a correct way of autoloading (or just loading) a class to be used as part of a test both globally and per suite?
Am I on the right track overall in abstracting my tests like this? TDD is new to me and I am trying to better my development workflow (with help from Laracasts).
I've searched every where for an answer to load the classes I need but usually all I'll find is PHPUnit specific answers which don't appear to work. I have also peered through the Codeception documentation which feels a bit sparse on the subject and the API docs don't explain the method call usage in the case of Autoload::load
Cheers,
- Everon.

You can do this for your whole test suit, or just individual components. For example, for Unit tests only, do the following:
Add bootstrap: my_bootstrap_file.php to tests/unit.suite.yml:
# Codeception Test Suite Configuration
#
# Suite for unit or integration tests.
actor: UnitTester
bootstrap: my_bootstrap_file.php
modules:
enabled:
- Asserts
- \Helper\Unit
Call my_bootstrap_file.php something sensible like just bootstrap.php
Create tests/unit/my_bootstrap_file.php
<?php
\Codeception\Util\Autoload::addNamespace('', 'src');
The directory structure should look like this:
<project root>
src/
tests/
unit/
my_bootstrap_file.php
unit.suite.yml
Replace unit in the instructions above with acceptance, functional, etc. to apply it to different single components.
The PhpDoc for \Codeception\Util\Autoload::addNamespace():
/**
* Adds a base directory for a namespace prefix.
*
* Example:
*
* ```php
* <?php
* // app\Codeception\UserHelper will be loaded from
* '/path/to/helpers/UserHelper.php'
*
* Autoload::addNamespace('app\Codeception', '/path/to/helpers');
*
* // LoginPage will be loaded from '/path/to/pageobjects/LoginPage.php'
* Autoload::addNamespace('', '/path/to/pageobjects');
*
* Autoload::addNamespace('app\Codeception', '/path/to/controllers');
* ?>
* ```
*
* #param string $prefix The namespace prefix.
* #param string $base_dir A base directory for class files in the namespace.
* #param bool $prepend If true, prepend the base directory to the stack instead
* of appending it; this causes it to be searched
* first rather than last.
* #return void
*/
public static function addNamespace($prefix, $base_dir, $prepend = false)
If you want this to apply to your whole test suite, not just Unit tests, use codeception.yml instead of tests/unit.suite.yml, and tests/my_bootstrap_file.php instead of tests/unit/my_bootstrap_file.php.
<project root>
src/
tests/
my_bootstrap_file.php
codeception.yml

Related

PHPStan configuration for FuelPHP

I am having a lot of trouble getting PHPStan to see the FuelPHP core classes. It would appear this sort of thing causes it grief:
namespace Fuel\Core;
/**
* Template Controller class
*
* A base controller for easily creating templated output.
*
* #package Fuel
* #category Core
* #author Fuel Development Team
*/
abstract class Controller_Template extends \Controller
{
/**
* #var string page template
*/
public $template = 'template';
Where Controller is also in the Fuel\Core namespace:
namespace Fuel\Core;
abstract class Controller
{
/**
* #var Request The current Request object
*/
public $request;
It looks like PHPStan can's find Controller because it is looking in the root namespace. FuelPHP gets around this (magic? autoloading? aliasing?). Is there a way to get PHPStan to jump on the same bandwagon, or do I need to stub out all the core classes I'm using?
Did you try to follow this guide? PHPStan: Discovering symbols
I helped set up FuelCMS analysis in the past. What worked for that user was this phpstan.neon:
parameters:
scanDirectories:
- core/classes
bootstrapFiles:
- core/classes/autoloader.php
There's an example repository that works: https://github.com/ondrejmirtes/phpstan_problem/tree/fix
For some reason the phpstan.neon is buried in app/classes/controller while it should definitely be in the root directory. But otherwise it works.

PHPDocumenter No summary for class

I am using PHPDocuementer and I keep getting these messages for my classes:
Parsing /code/vendor/prodigyview/helium/app.class.php
No summary for class \prodigyview\helium\He2App
But when code is the following:
<?php
/**
* The main application for instantiaing the He2MVC Framework and bringing together the parts required for the system to work.
*
* The application is what is called with Helium is first initiliaed in the frontend controller. It will autoload the components,
* set the registry and then send the application into the router. The boostrap of the framework should be called sometime during
* this point.
*
* #package prodigyview\helium
*/
namespace prodigyview\helium;
class He2App extends \PVStaticInstance {
}
Am I missing something?
The one docblock you have should be moved down to just above the class line. Its current location is being interpreted as the file-level docblock rather than a class-level docblock.
After doing this, you might start seeing warnings about file-level docblock missing. Just add a new docblock at the same place you have the current example one. You could use an exact copy of the one you already have up there, since its content seems to fit for the file as well as the class.

Codeception DataFactory interaction between helper and other factories

I'm looking into using DataFactory's in Codeception for seeding of data, and for use in our acceptance tests. In the documentation there's mention of 2 approaches, one using the helper file and one using factories files.
We load both options using this snippet from our acceptance.suite.yml
class_name: AcceptanceTester
modules:
enabled:
- Db
- WebDriver
- \Helper\Acceptance
- Doctrine2:
connection_callback: getEntityManager
- DataFactory:
factories: tests/_support/factories
depends: Doctrine2
- \Helper\Factory
Both of the options seem to load correctly. As per the documentation I can then define factories like this, which will allow interaction with Doctrine.
// tests/_support/Helper/Factory.php
class Factory extends Module
{
/**
* #param array $settings
* #throws \League\FactoryMuffin\Exceptions\DefinitionAlreadyDefinedException
* #throws \Codeception\Exception\ModuleException
*/
public function _beforeSuite($settings = [])
{
/** #var Module\DataFactory $factory */
$factory = $this->getModule('DataFactory');
/** #var EntityManager $em */
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(User::class,[
// generate random user name
'name' => Faker::name(),
]);
parent::_beforeSuite($settings);
}
}
As per the other option, I can also create factories by loading all files from within tests/_support/factories, such as below:
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
/** #var \League\FactoryMuffin\FactoryMuffin $fm */
$user = $fm->create(User::class);
dd($user);
However, the seed.php version cannot seem to share the Factory, and errors with:
The model definition 'User' is undefined.
I wondered if maybe this could be solved by moving the Factory.php logic into the initialize() method but this seems to be called before FactoryMuffin has been initiliazed.
The documentation for this with codeception seems a bit sparse, and the FactoryMuffin docs, while better, don't cover Codeception integration. Just trying to work out if i'm missing something, or I just need to repeat the code in each place if I want to use both files/methods.
This is an old question and technology moves fast so the documentation has likely changed since this was originally asked but I'll make an attempt in case anyone else stumbles across it like I did.
You're using the DataFactory module which is great as it comes with the integration for Codeception out of the box. The two methods you've described are actually ways of integrating DataFactory with your data. By creating factory files, you've given DataFactory a means of generating data. But what if you have some data already in the database that you'd like to use in your tests as well? That's where you would use the Helper class. According to the DataFactory Module docs:
In cases you want to use data from database inside your factory definitions you can define them in Helper. For instance, if you use Doctrine, this allows you to access EntityManager inside a definition.
As for your issue of seed.php not finding the User model, you need to specify it according to the definition given in your factory. For example, if your factory file looks similar to this
<?php
use League\FactoryMuffin\Faker\Facade as Faker;
$fm->define('app\models\User')->setDefinitions([
'name' => Faker::name(),
... // the rest of your properties here
]);
Then seed.php would look like
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
$user = $fm->create('app\models\User');
Once you have the DataFactory module installed and configured, you can simply call it within the appropriate testing suite via have, haveMultiple, or make. See the Codeception Docs

Interface '...' not found in (PHP SQL Query Builder)

I'm trying to use PHP SQL Query Builder in a project I'm working on. I am installing it manually. I did this by copying the src directory into my project and then using the following code within my class:
$this->builder = new NilPortugues\Sql\QueryBuilder\Builder\GenericBuilder();
The error I get whenever the builder is used is:
[18-Apr-2017 12:57:48 UTC] PHP Fatal error: Interface 'NilPortugues\Sql\QueryBuilder\Builder\BuilderInterface' not found in /home/thomassm/public_html/php/lib/sqlbuilder/Builder/GenericBuilder.php on line 24
GenericBuilder.php
namespace NilPortugues\Sql\QueryBuilder\Builder;
use NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory;
use NilPortugues\Sql\QueryBuilder\Manipulation\AbstractBaseQuery;
use NilPortugues\Sql\QueryBuilder\Manipulation\QueryInterface;
use NilPortugues\Sql\QueryBuilder\Manipulation\QueryFactory;
use NilPortugues\Sql\QueryBuilder\Manipulation\Select;
use NilPortugues\Sql\QueryBuilder\Syntax\Column;
use NilPortugues\Sql\QueryBuilder\Syntax\Table;
/**
* Class Generic.
*/
class GenericBuilder implements BuilderInterface
{//...}
BuilderInterface.php
namespace NilPortugues\Sql\QueryBuilder\Builder;
use NilPortugues\Sql\QueryBuilder\Manipulation\QueryInterface;
/**
* Interface BuilderInterface.
*/
interface BuilderInterface
{
/**
* #param QueryInterface $query
*
* #return string
*/
public function write(QueryInterface $query);
/**
* #param QueryInterface $query
*
* #return string
*/
public function writeFormatted(QueryInterface $query);
}
I assume the error is somehow caused by the way the files are called, any suggestions?
I am installing it manually. I did this by copying the src directory into my project and then using the following code within my class: ...
Every source file should be included through include/requrie before using. But PHP allow to setup autoloading for classes, interfaces and traits. In short, when runtime meets an undefined class then a special callback invokes, which can load the relevant file.
Although formally the autoloading rules can be arbitrary, but most of the modern projects supports the common community standard: PSR-4 Autoloader. So, you need an implementation of this standard.
As said in the library description, the recommended way to install is through Composer:
php composer.phar require nilportugues/sql-query-builder
Composer provides own PSR-4 implementation and generates file vendor/autoload.php. Usually this file included at the application entry point. It allow using the classes from all required libraries.
You can use the packages without the Composer (relevant answer). But it requires a lot of work (you still need an PSR-4 autoloader) and this way is rarely used today.

Behat - Implement Step definitions

I am having difficulties in setting Behat framework correctly. The test runs fine, however the step definitions are not being picked up from the FeatureContext file. Have tried out answers from other questions with no luck. The behat.yml file is currently stored in the root of the project:
default:
paths:
features: 'bin/features'
bootstrap: 'bin/features/bootstrap'
context:
class: 'FeatureContext'
extensions:
Behat\MinkExtension\Extension:
goutte: ~
selenium2: ~
In the root of the project I have a bin folder which contains the standard behat files: behat.bat, webunit.bat etc. Within the bin folder I have the features folder which contains the file search.feature:
Feature: Search
In order to find a word
As a website user
I need to be able to search for a word
#javascript
Scenario: Searching for a word that does exist
Given I am on "http://drupalcamp.mx/search/"
When I fill in "Escriba las palabras clave" with "behat, mink"
And I press "Buscar"
Then I should see "Behavior Driven Development"
And I follow "Behavior Driven Development"
And I wait for 5 seconds
Within the features folder I there is a bootstrap folder which contains the file "FeatureContext"
namespace features;
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
use Behat\MinkExtension\Context\MinkContext;
/**
* Features context.
*/
class FeatureContext extends MinkContext
{
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* #param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $parameters)
{
// Initialize your context here
}
/**
* #Given /^I wait for (\d+) seconds$/
*/
public function iWaitForSeconds($seconds)
{
$this->getSession()->wait($seconds*1000);
}
When I run "behat" from the root directory of the project in CL, it fires the test in the browser, passes all but fails to recognise the step definition, it states "You can implement step definitions for undefined steps with these snippets:" which then it gives an example.
You need to configure the autoloader to load classes from your custom bootstrap folder.
If you're using composer, here's an example of how it could be done:
"autoload": {
"psr-0": { "": ["src/", "bin/features/bootstrap/"]}
}
Note: bin folder is a really odd location to put your features or context files.
Related question with an answer: Behat + Symfony, Custom definitions are not loaded (actually, might be a duplicate).

Categories