“yiisoft/yii2-codeception”: “*”
from section require-dev
in app/composer.json
. base
):base
: $ composer remove --dev codeception/base
$ composer requires --dev codeception/codeception
require-dev
in @app/composer.json
:"require-dev": { "codeception/base": "^2.2.3", "codeception/codeception": "2.3.*", "codeception/specify": "*", "codeception/verify": "*", ... }
$ composer update
.Run the following commands to install Codeception globally (when sharing the same installation among several projects):
%composer global require "codeception/codeception=2.3.*" %composer global require "codeception/specify=*" %composer global require "codeception/verify=*"
svr$ java -jar ~/selenium-server-standalone-x.xx.x.jar
svr$ java -jar -Dwebdriver.gecko.driver=~/geckodriver ~/selenium-server-standalone-3.xx.x.jar C:\> java -jar -Dwebdriver.gecko.driver=C:\apps\Selenium\geckodriver C:\apps\Selenium\selenium-server-standalone-3.xx.x.jar
svr$ java -jar -Dwebdriver.chrome.driver=~/chromedriver ~/selenium-server-standalone-3.xx.x.jar C:\> java -jar -Dwebdriver.chrome.driver=C:\apps\Selenium\chromedriver C:\apps\Selenium\selenium-server-standalone-3.xx.x.jar
You can run the Codeception base tests:
$ cd ~/proj/myapp/ $ ./vendor/bin/codecept run
If this fails, check or generate the configuration files for the tests.
NOTE: Yii 2.0.12 already comes with the tests in [app]/tests
folder without the need to bootstrap or build these. However, if wanting to generate test structure from scratch (not the actual tests), then follow the next set of instructions.
Run Codeception bootstrap to generate configuration:
svr$ cd ~/proj/myapp/ svr$ ./vendor/bin/codecept bootstrap C:\> cd C:\proj\myapp C:\> .\vendor\bin\codecept bootstrap
This generates the required files:
C:\wamp\www\myapp>codecept bootstrap Initializing Codeception in C:\wamp\www\myapp File codeception.yml created <- global configuration tests/unit created <- unit tests tests/unit.suite.yml written <- unit tests suite configuration tests/functional created <- functional tests tests/functional.suite.yml written <- functional tests suite configuration tests/acceptance created <- acceptance tests tests/acceptance.suite.yml written <- acceptance tests suite configuration --- tests/_bootstrap.php written <- global bootstrap file Building initial Tester classes Building Actor classes for suites: acceptance, functional, unit -> AcceptanceTesterActions.php generated successfully. 0 methods added \AcceptanceTester includes modules: PhpBrowser, \Helper\Acceptance AcceptanceTester.php created. -> FunctionalTesterActions.php generated successfully. 0 methods added \FunctionalTester includes modules: \Helper\Functional FunctionalTester.php created. -> UnitTesterActions.php generated successfully. 0 methods added \UnitTester includes modules: Asserts, \Helper\Unit UnitTester.php created. Bootstrap is done. Check out C:\wamp\www\myapp/tests directory
It should generate the following structure:
myapp/tests/
myapp/tests/functional/
myapp/tests/acceptance/
myapp/tests/unit/
myapp/codeception.yml
(main configuration)myapp/tests/unit.suite.yml
myapp/tests/functional.suite.yml
myapp/tests/acceptance.suite.yml
This generates config files such as: [app]\codeception.yml
:
#actor: Tester #coverage: # #c3_url: http://localhost:8080/index-test.php/ # enabled: true # #remote: true # #remote_config: '../tests/codeception.yml' # white_list: # include: # - ../models/* # - ../controllers/* # - ../commands/* # - ../mail/* # blacklist: # include: # - ../assets/* # - ../config/* # - ../runtime/* # - ../vendor/* # - ../views/* # - ../web/* # - ../tests/* actor: Tester paths: tests: tests log: tests/_output data: tests/_data helpers: tests/_support envs: tests/_envs settings: bootstrap: _bootstrap.php suite_class: \PHPUnit_Framework_TestSuite memory_limit: 1024M log: true colors: false extensions: enabled: - Codeception\Extension\RunFailed config: # the entry script URL (with host info) for functional and acceptance tests # PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL test_entry_url: http://localhost:8080/myapp/web/index-test.php
[app]\tests\acceptance.suite.yml
:
# Codeception Test Suite Configuration # suite for acceptance tests. # perform tests in browser using the Selenium-like tools. # powered by Mink (http://mink.behat.org). # (tip: that's what your customer will see). # (tip: test your ajax and javascript by one of Mink drivers). # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. class_name: AcceptanceTester modules: enabled: #- PhpBrowser # url: 'http://localhost:8080/myapp/web' # You can use WebDriver instead of PhpBrowser to test javascript and ajax. # This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium # "restart" option is used by the WebDriver to start each time per test-file new session and cookies, # it is useful if you want to login in your app in each test. - WebDriver url: 'http://localhost:8080/myapp/web' # optional browser: firefox # optional config: #PhpBrowser: # url: 'http://localhost:8080/myapp/web' WebDriver: url: 'http://localhost:8080/myapp/web' browser: firefox restart: true
[app]\tests\functions.suite.yml
:
# Codeception Test Suite Configuration # suite for functional (integration) tests. # emulate web requests and make application process them. # (tip: better to use with frameworks). # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. #basic/web/index.php class_name: FunctionalTester modules: enabled: - Filesystem - Yii2 #part: [init, orm, email, fixtures] configFile: 'config/test.php' entryScript: index-test.php - Db: dsn: "mysql:host=localhost;dbname=mydatabase" user: "dbuser" password: "dbsecret" #- REST # depends: PhpBrowser # url: http://localhost:8080/yii/basic-userdb/web/
[app]\tests\unit.suite.yml
:
# Codeception Test Suite Configuration # suite for unit (internal) tests. # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. class_name: UnitTester modules: enabled: - Asserts - Yii2: part: [orm, email] configFile: 'config/test.php' entryScript: index-test.php #- Db: # dsn: "mysql:host=localhost;dbname=mydatabase" # user: "dbuser" # password: "dbsecret"
Verify database configuration in [app]/config/test_db.php
:
<?php $db = require(__DIR__ . '/db.php'); // test database! Important not to run tests on production or development databases //$db['dsn'] = 'mysql:host=localhost;dbname=yii2_basic_tests'; $db['dsn'] = 'mysql:host=localhost;dbname=acme_myapp_test'; return $db;
Perform database migration if necessary:
C:\> yii migrate/to m170227_101010_create_tables
or simply:
C:\> yii migrate
Build test suite:
svr$ ./vendor/bin/codecept build C:\> cd C:\proj\myapp\tests C:\> codecept build
NOTE: Run build
command after adding/removing modules in files:
[app]/tests/unit.suite.yml
[app]/tests/functional.suite.yml
[app]/tests/acceptance.suite.yml
This command generates:
C:\wamp\www\myapp\tests>codecept build Building Actor classes for suites: acceptance, functional, unit -> AcceptanceTesterActions.php generated successfully. 0 methods added \AcceptanceTester includes modules: PhpBrowser, \Helper\Acceptance -> FunctionalTesterActions.php generated successfully. 0 methods added \FunctionalTester includes modules: \Helper\Functional -> UnitTesterActions.php generated successfully. 0 methods added \UnitTester includes modules: Asserts, \Helper\Unit
Some support files can be found in [app]/tests/_support/_generated
. They include the commands available to be used in testing.
To execute unit and functional tests:
# run only unit and functional tests svr$ cd ~/proj/myapp svr$ ./vendor/bin/codecept run unit,functional C:\> cd C:\proj\myapp C:\> .\vendor\bin\codecept run unit,functional
To execute all tests:
# run all available tests svr$ ./vendor/bin/codecept run # run tests with verbose output svr$ ./vendor/bin/codecept run --debug --fail-fast # run tests with code coverage svr$ ./vendor/bin/codecept run --coverage-html --coverage-xml
To execute functional tests:
# run acceptance tests svr$ ./vendor/bin/codecept run functional
To execute acceptance tests:
# run acceptance tests svr$ ./vendor/bin/codecept run acceptance # run only acceptance test LoginCept svr$ ./vendor/bin/codecept run acceptance LoginCept
Acceptance testing requires a webserver. Run a built-in PHP webserver (accessible on port 88 for any interface):
# Alternative 1 # run built-in PHP webserver # see: http://php.net/manual/en/features.commandline.webserver.php $php -S 0.0.0.0:88 -t [app]/web/ # Alternative 2 # run built-in Yii webserver # edit default port in [app]\vendor\yiisoft\yii2\console\controllers\ServeController.php # run default port: svr$ ./yii serve svr$ ./yii serve localhost:88
Generate CEPT acceptance test (for use with single test):
svr$ ./vendor/bin/codecept generate:cept acceptance Welcome Test was created in C:/proj/myapp/tests/acceptance/WelcomeCept.php
Generated code:
<?php $I = new AcceptanceTester($scenario); $I->wantTo('perform actions and see result'); // Add something $I->amOnPage('/'); $I->see('Welcome');
Generate CEST acceptance test (for use with multiple tests):
svr$ ./vendor/bin/codecept generate:cest acceptance HelloWorld
Test:
<?php class HelloWorldCest { public function _before(AcceptanceTester $I) { $I->amOnPage('/forgotten') } public function _after(AcceptanceTester $I) { } // tests public function testEmailField(AcceptanceTester $I) { $I->see('Enter email'); } public function testIncorrectEmail(AcceptanceTester $I) { $I->fillField('email', 'incorrect@email.com'); $I->click('Continue'); $I->see('Email is incorrect, try again'); } public function testCorrectEmail(AcceptanceTester $I) { $I->fillField('email', 'correct@email.com'); $I->click('Continue'); $I->see('Please check your email for next instructions'); } }
Generate CEPT functional test (for use with single test):
svr$ ./vendor/bin/codecept generate:cept functional HelloWorld
Generated code:
<?php $I = new FunctionalTester($scenario); $I->amOnPage('/'); $I->see('Welcome');
Generate CEST functional test (for use with multiple tests):
svr$ ./vendor/bin/codecept generate:cest functional HelloWorld
Generate unit test:
# To generate PHPUnit test inside [app]/test/unit dir svr$ vendor/bin/codecept generate:phpunit unit HelloWorld # To generate PHPUnit test inside [app]/test/unit/models dir svr$ ./vendor/bin/codecept generate:phpunit unit models/HelloWorld
Or simply inherit your tests on \PHPUnit_Framework_TestCase
Alternatively, generate Codeception unit tests:
# To generate Codeception unit test inside [app]/test/unit dir svr$ ./vendor/bin/codecept generate:test unit HelloWorld
Generated code:
<?php class AddressTest extends \Codeception\Test\Unit { /** * @var \UnitTester */ protected $tester; protected function _before() { } protected function _after() { } // tests public function testSomeFeature() { } }
You might need to add the correct namespace for tests and models:
<?php namespace test\models; use app\models\User; // to access User model use app\models\Address; // to access Address model //...
When running a unit test, you get “Fatal error: Class 'tests\codeception\unit\models\TestCase' not found in C:\wamp\www\myapp\tests\codeception\unit\models\ContactFormTest.php on line 11”, you are probably running an out of date version of tests suite and/or Codeception.
You need to upgrade to yii-basic-template
2.0.12 or higher, and Codeception to 2.3.6 or higher. Follow these steps:
[app]/composer.json
to be: Add support for codeception/codeception to section require-dev in app/composer.json: "require-dev": { "codeception/base": "^2.2.3", "codeception/codeception": "2.3.*", "codeception/specify": "*", "codeception/verify": "*", ... }
[app]/tests
folder.[app]/tests
folder from new download.[app]/codeception.yml
file from form new download.[app]/config/test.php
and [app]/config/test_db.php
files from form new download.[app]/web/index-test.php
to look as follows: <?php // NOTE: Make sure this file is not accessible when deployed to production if (!in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) { die('You are not allowed to access this file.'); } defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'test'); require(__DIR__ . '/../vendor/autoload.php'); require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); // Remove: //$config = require(__DIR__ . '/../tests/codeception/config/acceptance.php'); // Add: $config = require(__DIR__ . '/../config/test.php'); (new yii\web\Application($config))->run();
Output messages:
// Statement to output message to Codeception debugger codecept_debug("debug message");
Run codecept in debug mode. Eg:
svr$ ./vendor/bin/codecept run unit --debug
Uses some of these most common asserts:
$this->assertEquals() $this->assertContains() $this->assertFalse() $this->assertTrue() $this->assertNull() $this->assertEmpty()
For BDD (Behavior Driven Development), add the specifications first, then build the tests into each spec.
<?php // this is just a PHPUnit's testcase class PostTest extends PHPUnit_Framework_TestCase { use Codeception\Specify; // just a regular test declaration public function testPublication() { $this->specify('post can be published'); $this->specify('post should contain a title'); $this->specify('post should contain a body'); $this->specify('author of post should not be banned'); } } ?>
Add asserts under each spec. Group specs under a single behavior test (following BDD convention).
<?php // this is just a PHPUnit's testcase class PostTest extends PHPUnit_Framework_TestCase { use Codeception\Specify; // just a regular test declaration public function testPublication() { $this->post = new Post; $this->post->setAuthor(new User()); $this->specify('post can be published', function() { $this->post->setTitle('Testing is Fun!'); $this->post->setBody('Thats for sure'); $this->assertTrue($this->post->publish()); }); $this->specify('post should contain a title', function() { $this->assertFalse($this->post->publish()); $this->assertArrayHasKey('title', $this->post->errors()); }); $this->specify('post should contain a body', function() { $this->assertFalse($this->post->publish()); $this->assertArrayHasKey('body', $this->post->errors()); }); $this->specify('author of post should not be banned', function() { $this->post->getAuthor()->setIsBanned(true); $this->post->setTitle('Testing is Fun!'); $this->post->setBody('Thats for sure'); $this->assertFalse($this->post->publish()); $this->assertArrayHasKey('author', $this->post->errors()); }); } } ?>
Change the asserts to follow the expect(XX)→toBe(YY)
style, more in line with BDD.
<?php // this is just a PHPUnit's testcase class PostTest extends PHPUnit_Framework_TestCase { use Codeception\Specify; // just a regular test declaration public function testPublication() { $this->post = new Post; $this->post->setAuthor(new User()); $this->specify('post can be published', function() { $this->post->setTitle('Testing is Fun!'); $this->post->setBody('Thats for sure'); expect_that($this->post->publish()); }); $this->specify('post should contain a title', function() { expect_not($this->post->publish()); expect($this->post->errors())->hasKey('title'); }); $this->specify('post should contain a body', function() { expect_not($this->post->publish()); expect($this->post->errors())->hasKey('body'); }); $this->specify('author of post should not be banned', function() { $this->post->getAuthor()->setIsBanned(true); $this->post->setTitle('Testing is Fun!'); $this->post->setBody('Thats for sure'); expect_not($this->post->publish()); expect($this->post->errors())->hasKey('author'); }); } } ?>
What style to use depends on what needs testing. For features, use BDD features scenarios (i.e. anything that bring business value). For the rest (such as regression tests or negative scenario tests) which have no direct business value, use Cept/Cest/Test formats.
Fixtures help generate data models that can be used for testing. Create a fixture and place it in @app/tests/fixtures/
. Eg: File @app/tests/fixtures/PriceFixture.php
:
<?php namespace app\tests\fixtures; use yii\test\ActiveFixture; class PriceFixture extends ActiveFixture { public $modelClass = 'app\models\Price'; }
Add fixture data file. The data file should return an array of data rows to be inserted into the respective table. Eg: @tests/fixtures/data/price.php
(or @tests/_data/price.php
if using Codeception). Notice the optional alias for each row, i.e. price1
, price2
, etc.
<?php return [ 'price1' => [ 'item_code' => 'TABLE_BLACK', 'description' => 'Black Table', 'cost' => '120', ], 'price2' => [ 'item_code' => 'TABLE_BEIGE', 'description' => 'Beige Table', 'cost' => '110', ], ];
Include fixtures in Codeception unit test. Eg: File @app/tests/unit/models/PriceTest.php
:
<?php namespace models; use app\models\Price; use app\tests\fixtures\PriceFixture; class PriceTest extends \Codeception\Test\Unit { /** * @var \UnitTester */ protected $tester; public function _fixtures() { return [ 'prices' => [ // <-- alias for this data 'class' => PriceFixture::className(), // Codeception fixture data located in @tests/_data/price.php // Default is usually @tests/unit/fixtures/data/price.php 'dataFile' => codecept_data_dir() . 'price.php' ], ]; } //... } ?>
Source: Yii 2 Fixtures
Install yii2-faker
extension (comes preinstalled with Yii2). Add the following to the @app/config/console.php
file:
'controllerMap' => [ 'fixture' => [ 'class' => 'yii\faker\FixtureController', 'templatePath' => '@app/tests/unit/templates/fixtures', //'fixtureDataPath' => '@app/tests/fixtures/data', 'fixtureDataPath' => '@app/tests/_data', ], ],
In the unit test (or any part of the application), you can use faker like this:
// use the factory to create a Faker\Generator instance $faker = \Faker\Factory::create(); // generate data by accessing properties echo $faker->name; echo $faker->address; echo $faker->text;
Get more generators from: https://github.com/fzaninotto/Faker
If you need unit tests to load faker data using fixtures, create a fixture template under @tests/unit/templates/fixtures
. Eg: File users.php
:
<?php // users.php file under the template path (by default @tests/unit/templates/fixtures) /** * @var $faker \Faker\Generator * @var $index integer */ return [ 'name' => $faker->firstName, 'phone' => $faker->phoneNumber, 'city' => $faker->city, 'password' => Yii::$app->getSecurity()->generatePasswordHash('password_' . $index), 'auth_key' => Yii::$app->getSecurity()->generateRandomString(), 'intro' => $faker->sentence(7, true), // generate a sentence with 7 words ];
Call fixture from unit test:
public function _fixtures() { return [ 'profiles' => [ // alias for this data 'class' => UserFixture::className(), // Codeception fixture data located in @tests/_data/users.php 'dataFile' => codecept_data_dir() . 'users.php' ], ]; }
With a template file, generate fixtures using any of the following commands.
Generate fixtures from users fixture template
$ yii fixture/generate users
Generate several fixture data files. Eg: users
is a template name. The command generates a new file with the same template name under the fixture path (@tests/unit/fixtures
folder).
$ yii fixture/generate users profiles teams
This command will generate fixtures for all template files that are stored under the template path and store fixtures under the fixtures path with file names same as templates names.
$ yii fixture/generate-all
Generate N number of fixtures per file
$ yii fixture/generate-all --count=3
Generate fixtures in russian language
$ yii fixture/generate users --count=5 --language="ru_RU"
Read templates from the other path
$ yii fixture/generate-all --templatePath='@app/path/to/my/custom/templates'
Generate fixtures into other directory.
$ yii fixture/generate-all --fixtureDataPath='@tests/acceptance/fixtures/data'
These are the common test doubles (Source: https://martinfowler.com/articles/mocksArentStubs.html):
Check