Testomatio - Test Management for Codeception
Codeception uses modularity to create a comfortable testing environment for every test suite you write.
All actions and assertions that can be performed by the Tester object in a class are defined in modules. You can extend the testing suite with your own actions and assertions by writing them into a custom module.
Letâs look at the following test:
$I->amOnPage('/');
$I->see('Hello');
$I->seeInDatabase('users', array('id' => 1));
$I->seeFileFound('running.lock');
It can operate with different entities: the web page can be loaded with the PhpBrowser module, the database assertion uses the Db module, and file state can be checked with the Filesystem module.
Modules are attached to the Actor classes in the suite configuration.
For example, in tests/Functional.suite.yml
we should see:
actor: FunctionalTester
modules:
enabled:
- PhpBrowser:
url: http://localhost
- Db:
dsn: "mysql:host=localhost;dbname=testdb"
- Filesystem
The FunctionalTester class has its methods defined in modules. Actually, it doesnât contain any of them,
but rather acts as a proxy. It knows which module executes this action and passes parameters into it.
To make your IDE see all of the FunctionalTester methods, you should run the codecept build
command.
It generates method signatures from enabled modules and saves them into a trait which is included in an actor.
In the current example, the tests/support/_generated/FunctionalTesterActions.php
file will be generated.
By default, Codeception automatically rebuilds the Actions trait on each change of the suite configuration.
Codeception has many bundled modules which will help you run tests for different purposes and different environments. The idea of modules is to share common actions, so that developers and QA engineers can concentrate on testing and not on reinventing the wheel. Each module provides methods for testing its own part and by combining modules you can get a powerful setup to test an application at all levels.
There is the WebDriver
module for acceptance testing, modules for all popular PHP frameworks,
PHPBrowser
to emulate browser execution, REST
for testing APIs, and more.
Modules are considered to be the most valuable part of Codeception.
They are constantly improving to provide the best testing experience, and be flexible to satisfy everyoneâs needs.
Modules may conflict with one another. If a module implements Codeception\Lib\Interfaces\ConflictsWithModule
,
it might declare a conflict rule to be used with other modules. For instance, WebDriver conflicts
with all modules implementing the Codeception\Lib\Interfaces\Web
interface.
public function _conflicts()
{
return 'Codeception\Lib\Interfaces\Web';
}
This way if you try to use two modules sharing the same conflicted interface you will get an exception.
To avoid confusion, Framework modules, PhpBrowser, and WebDriver canât be used together. For instance,
the amOnPage
method exists in all those modules, and you should not try to guess which module will actually execute it.
If you are doing acceptance testing, set up either WebDriver or PHPBrowser but do not set both up at the same time.
If you are doing functional testing, enable only one of the framework modules.
In case you need to use a module which depends on a conflicted one, specify it as a dependent module in the configuration.
You may want to use WebDriver
with the REST
module which interacts with a server through PhpBrowser
.
In this case your config should look like this:
modules:
enabled:
- WebDriver:
browser: firefox
url: http://localhost
- REST:
url: http://localhost/api/v1
depends: PhpBrowser
This configuration will allow you to send GET/POST requests to the serverâs APIs while working with a site through a browser.
If you only need some parts of a conflicted module to be loaded, refer to the next section.
Modules with Parts section in their reference can be partially loaded. This way, the $I
object will have actions
belonging to only a specific part of that module. Partially loaded modules can be also used to avoid module conflicts.
For instance, the Laravel module has an ORM part which contains database actions. You can enable the PhpBrowser module for testing and Laravelâs ORM part for connecting to the database and checking the data.
modules:
enabled:
- PhpBrowser:
url: http://localhost
- Laravel:
part: ORM
The modules wonât conflict as actions with the same names wonât be loaded.
The REST module has parts for Xml
and Json
in the same way. If you are testing a REST service with only JSON responses,
you can enable just the JSON part of this module:
actor: ApiTester
modules:
enabled:
- REST:
url: http://serviceapp/api/v1/
depends: PhpBrowser
part: Json
Codeception doesnât restrict you to only the modules from the main repository.
Your project might need your own actions added to the test suite. By running the codecept generate:helper Name
command,
you can generate custom module called âHelperâ in tests/Support/Helper
directory.
<?php
namespace Tests\Support\Helper;
// here you can define custom functions for FunctionalTester
class Functional extends \Codeception\Module
{
}
Actions are also quite simple. Every action you define is a public function. Write a public method,
then run the build
command, and you will see the new function added into the FunctionalTester class.
Assertions can be a bit tricky. First of all, itâs recommended to prefix all your assertion actions with see
or dontSee
.
Name your assertions like this:
$I->seePageReloaded();
$I->seeClassIsLoaded($classname);
$I->dontSeeUserExist($user);
And then use them in your tests:
$I->seePageReloaded();
$I->seeClassIsLoaded('FunctionalTester');
$I->dontSeeUserExist($user);
You can define assertions by using assertXXX methods in your modules.
function seeClassExist($class)
{
$this->assertTrue(class_exists($class));
}
In your helpers you can use these assertions:
<?php
function seeCanCheckEverything($thing)
{
$this->assertTrue(isset($thing), "this thing is set");
$this->assertFalse(empty($any), "this thing is not empty");
$this->assertNotNull($thing, "this thing is not null");
$this->assertContains("world", $thing, "this thing contains 'world'");
$this->assertNotContains("bye", $thing, "this thing doesn't contain 'bye'");
$this->assertEquals("hello world", $thing, "this thing is 'Hello world'!");
// ...
}
Itâs possible that you will need to access internal data or functions from other modules. For example, for your module you might need to access the responses or internal actions of other modules.
Modules can interact with each other through the getModule
method.
Please note that this method will throw an exception if the required module was not loaded.
Letâs imagine that we are writing a module that reconnects to a database. Itâs supposed to use the dbh connection value from the Db module.
function reconnectToDatabase()
{
$dbh = $this->getModule('Db')->dbh;
$dbh->close();
$dbh->open();
}
By using the getModule
function, you get access to all of the public methods and properties of the requested module.
The dbh
property was defined as public specifically to be available to other modules.
Modules may also contain methods that are exposed for use in helper classes. Those methods start with a _
prefix
and are not available in Actor classes, so can be accessed only from modules and extensions.
You should use them to write your own actions using module internals.
function seeNumResults($num)
{
// retrieving webdriver session
/**@var $table \Facebook\WebDriver\WebDriverElement */
$elements = $this->getModule('WebDriver')->_findElements('#result');
$this->assertNotEmpty($elements);
$table = reset($elements);
$this->assertEquals('table', $table->getTagName());
$results = $table->findElements('tr');
// asserting that table contains exactly $num rows
$this->assertEquals($num, count($results));
}
In this example we use the API of the facebook/php-webdriver library,
a Selenium WebDriver client the module is build on.
You can also access the webDriver
property of a module to get access to the Facebook\WebDriver\RemoteWebDriver
instance
for direct Selenium interaction.
If accessing modules doesnât provide enough flexibility, you can extend a module inside a Helper class:
<?php
namespace Tests\Support\Helper;
class MyExtendedSelenium extends \Codeception\Module\WebDriver
{
}
In this helper you can replace the parentâs methods with your own implementation.
You can also replace the _before
and _after
hooks, which might be an option
when you need to customize starting and stopping of a testing session.
Each module can handle events from the running test. A module can be executed before the test starts,
or after the test is finished. This can be useful for bootstrap/cleanup actions.
You can also define special behavior for when the test fails. This may help you in debugging the issue.
For example, the PhpBrowser module saves the current webpage to the tests/_output
directory when a test fails.
All hooks are defined in Codeception\Module and are listed here. You are free to redefine them in your module.
// HOOK: used after configuration is loaded
public function _initialize()
{
}
// HOOK: before each suite
public function _beforeSuite($settings = array())
{
}
// HOOK: after suite
public function _afterSuite()
{
}
// HOOK: before each step
public function _beforeStep(\Codeception\Step $step)
{
}
// HOOK: after each step
public function _afterStep(\Codeception\Step $step)
{
}
// HOOK: before test
public function _before(\Codeception\TestInterface $test)
{
}
// HOOK: after test
public function _after(\Codeception\TestInterface $test)
{
}
// HOOK: on fail
public function _failed(\Codeception\TestInterface $test, $fail)
{
}
Please note that methods with a _
prefix are not added to the Actor class.
This allows them to be defined as public but used only for internal purposes.
As we mentioned, the _failed
hook can help in debugging a failed test.
You have the opportunity to save the current testâs state and show it to the user, but you are not limited to this.
Each module can output internal values that may be useful during debug. For example, the PhpBrowser module prints the response code and current URL every time it moves to a new page. Thus, modules are not black boxes. They are trying to show you what is happening during the test. This makes debugging your tests less painful.
To display additional information, use the debug
and debugSection
methods of the module.
Here is an example of how it works for PhpBrowser:
$this->debugSection('Request', $params);
$this->client->request($method, $uri, $params);
$this->debug('Response Code: ' . $this->client->getStatusCode());
This test, running with the PhpBrowser module in debug mode, will print something like this:
I click "All pages"
* Request (GET) http://localhost/pages {}
* Response code: 200
Modules and Helpers can be configured from the suite configuration file, or globally from codeception.yml
.
Mandatory parameters should be defined in the $requiredFields
property of the class.
Here is how it is done in the Db module:
class Db extends \Codeception\Module
{
protected $requiredFields = ['dsn', 'user', 'password'];
// ...
}
The next time you start the suite without setting one of these values, an exception will be thrown.
For optional parameters, you should set default values. The $config
property is used to define optional parameters
as well as their values. In the WebDriver module we use the default Selenium Server address and port.
class WebDriver extends \Codeception\Module
{
protected $requiredFields = ['browser', 'url'];
protected $config = ['host' => '127.0.0.1', 'port' => '4444'];
// ...
}
The host and port parameter can be redefined in the suite configuration.
Values are set in the modules:config
section of the configuration file.
modules:
enabled:
- WebDriver:
url: 'http://mysite.com/'
browser: 'firefox'
- Db:
cleanup: false
repopulate: false
Optional and mandatory parameters can be accessed through the $config
property.
Use $this->config['parameter']
to get its value.
Modules can be dynamically configured from environment variables.
Parameter storage should be specified in the global codeception.yml
configuration inside the params
section.
Parameters can be loaded from environment vars, from yaml (Symfony format), .env (Laravel format), ini, or php files.
Use the params
section of the global configuration file codeception.yml
to specify how to load them.
You can specify several sources for parameters to be loaded from.
Example: load parameters from the environment:
params:
- env # load params from environment vars
Example: load parameters from YAML file (Symfony):
params:
- app/config/parameters.yml
Example: load parameters from php file (Yii)
params:
- config/params.php
Example: load parameters from .Env files (Laravel):
params:
- .env
- .env.testing
Once loaded, parameter variables can be used as module configuration values.
Use a variable name wrapped with %
as a placeholder and it will be replaced by its value.
Letâs say we want to specify credentials for a cloud testing service. We have loaded SAUCE_USER
and SAUCE_KEY
variables from environment, and now we are passing their values into config of WebDriver
:
modules:
enabled:
- WebDriver:
url: http://mysite.com
host: '%SAUCE_USER%:%SAUCE_KEY%@ondemand.saucelabs.com'
Parameters are also useful to provide connection credentials for the Db
module (taken from Laravelâs .env files):
modules:
enabled:
- Db:
dsn: "mysql:host=%DB_HOST%;dbname=%DB_DATABASE%"
user: "%DB_USERNAME%"
password: "%DB_PASSWORD%"
Parameters can set in JSON format. So objects, arrays, booleans and strings can also be passed into config.
modules:
enabled:
- \Tests\Support\DataHelper:
users: "%USERS_ARRAY%"
books: "%BOOKS_ARRAY%"
In this case USER_ARRAY
and BOOKS_ARRAY
are JSON-fromatted arrays. Codeception turns them into array while parsing configuration.
If you want to reconfigure a module at runtime, you need to call a helper that uses the _reconfigure
method of the module.
In this example we change the root URL for PhpBrowser, so that amOnPage('/')
will open /admin/
.
$this->getModule('PhpBrowser')->_reconfigure(['url' => 'http://localhost/admin']);
Usually, these configuration changes are effective immediately. However, in WebDriver configuration changes canât be applied that easily.
For instance, if you change the browser you need to close the current browser session and start a new one.
For that, WebDriver module provides a _restart
method which takes a config array and restarts the browser:
// start chrome
$this->getModule('WebDriver')->_restart(['browser' => 'chrome']);
// or just restart browser
$this->getModule('WebDriver')->_restart();
You cannot change static test configurations like
depends
at runtime.
At the end of a test all configuration changes will be rolled back to the original configuration values.
Sometimes it is needed to set custom configuration for a specific test only.
For Cest and Test\Unit
formats you can use #[Prepare]
attribute which executes the code before other hooks are executed. This allows #[Prepare]
to change the module configuration in runtime. #[Prepare]
uses dependency injection
to automatically inject required modules into a method.
To run a specific test only in Chrome browser, you can call _reconfigure
from WebDriver module for a test itself using #[Prepare]
.
use Codeception\Attribute\Prepare;
#[Prepare('useChrome')]
public function chromeSpecificTest()
{
// ...
}
protected function useChrome(\Codeception\Module\WebDriver $webdriver)
{
// WebDriver was injected by the class name
$webdriver->_reconfigure(['browser' => 'chrome']);
}
Prepare methods can invoke all methods of a module, as well as hidden API methods (starting with _
). Use them to customize the module setup for a specific test.
To change module configuration for a specific group of tests use GroupObjects.
Modules are the real power of Codeception. They are used to emulate multiple inheritances for Actor classes (UnitTester, FunctionalTester, AcceptanceTester, etc). Codeception provides modules to emulate web requests, access data, interact with popular PHP libraries, etc. If the bundled modules are not enough for you thatâs OK, you are free to write your own! Use Helpers (custom modules) for everything that Codeception canât do out of the box. Helpers also can be used to extend the functionality of the original modules.