= Yii 2 Testing Setup = == Setup Environment == === Install Local Codeception == * Remove ''"yiisoft/yii2-codeception": "*"'' from section ''require-dev'' in ''app/composer.json''. * If performing Acceptance tests with Selenium, you need the full Codeception package (not just Codeception ''base''): * Remove ''base'': $ composer remove --dev codeception/base * Add support for full Codeception package: $ composer requires --dev codeception/codeception * Or edit section ''require-dev'' in ''@app/composer.json'': "require-dev": { "codeception/base": "^2.2.3", "codeception/codeception": "2.3.*", "codeception/specify": "*", "codeception/verify": "*", ... } * Run ''$ composer update''. === Install Global Codeception === 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=*" === Selenium === * Download Selenium browser automation server (standalone): [[http://www.seleniumhq.org/download]] * Run Selenium server: svr$ java -jar ~/selenium-server-standalone-x.xx.x.jar * However, for Selenium Server 3.0, when using any of these browsers, launch Selenium using the appropriate custom driver command: * **Mozilla Firefox** browser since v48, [[https://github.com/mozilla/geckodriver/releases|download GeckoDriver]], then: 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 * **Google Chrome** browser since v53, [[https://sites.google.com/a/chromium.org/chromedriver/downloads|download ChromeDriver]], then: 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 == Run Default Tests == 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. == Configure 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" == Database Config == Verify database configuration in ''[app]/config/test_db.php'': Perform database migration if necessary: C:\> yii migrate/to m170227_101010_create_tables or simply: C:\> yii migrate == Build Tests == 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. == Run Tests == 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 Tests == === Acceptance Tests === 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: 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: 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'); } } === Functional Tests === Generate CEPT functional test (for use with single test): svr$ ./vendor/bin/codecept generate:cept functional HelloWorld Generated code: amOnPage('/'); $I->see('Welcome'); Generate CEST functional test (for use with multiple tests): svr$ ./vendor/bin/codecept generate:cest functional HelloWorld === Unit Tests=== 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: You might need to add the correct namespace for tests and models: == Troubleshooting == 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: * Upgrade Codeception. Edit ''[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": "*", ... } * Delete ''[app]/tests'' folder. * Add a copy of ''[app]/tests'' folder from new download. * Add a copy of ''[app]/codeception.yml'' file from form new download. * Add a copy of ''[app]/config/test.php'' and ''[app]/config/test_db.php'' files from form new download. * Edit file ''[app]/web/index-test.php'' to look as follows: run(); === Debugging === 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 == Test Specification == === Traditional Testing === ==== Assertions ==== Uses some of these most common asserts: $this->assertEquals() $this->assertContains() $this->assertFalse() $this->assertTrue() $this->assertNull() $this->assertEmpty() === Specify (BDD style) === For BDD (Behavior Driven Development), add the specifications first, then build the tests into each spec. 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). 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()); }); } } ?> === Verify (BDD Style) === Change the asserts to follow the ''expect(XX)->toBe(YY)'' style, more in line with BDD. 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'); }); } } ?> Source: [[http://codeception.com/10-04-2013/specification-phpunit.html|New Fashioned Classics: BDD Specs in PHPUnit]] == What Test Style to Use? == 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 == 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'': 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. [ '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'': [ // <-- 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: [[http://www.yiiframework.com/doc-2.0/guide-test-fixtures.html|Yii 2 Fixtures]] == Fixtures: Generating Data with Faker == 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', ], ], === Usage === 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]] === Template for Fixtures === If you need unit tests to load faker data using fixtures, create a fixture template under ''@tests/unit/templates/fixtures''. Eg: File ''users.php'': $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' ], ]; } ==== Generate fixture data ==== 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' == Test Doubles == These are the common test doubles (Source: https://martinfowler.com/articles/mocksArentStubs.html): * **Dummy** objects are passed around but never actually used. Usually they are just used to fill parameter lists. * **Fake** objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example). * **Stubs** provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. * **Spies** are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent. * **Mocks** are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive. Check * [[https://codeception.com/docs/05-UnitTests#Stubs|Stubs in Codeception]]. * [[https://codeception.com/docs/05-UnitTests#Mocks|Mocks in Codeception]]. == Test Examples == * See: [[systems:yii2:Testing Examples]] == References == * [[https://github.com/yiisoft/yii2-app-basic/blob/master/README.md#testing|Yii2 Testing with Basic App Template]] * [[https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/start-testing.md|Yii2 Testing with Advanced App Template]] * [[http://codeception.com/for/yii|Codeception for Yii]] * [[http://codeception.com/docs/02-GettingStarted#Debugging|Codeception Getting Started]] * [[http://codeception.com/docs/modules/Yii2|Codeception Modules: Yii2]] * [[http://codeception.com/docs/07-BDD|Codeception using BDD]] * [[http://codeception.com/10-04-2013/specification-phpunit.html|Codeception using Specify & Verify (BDD Specs in PHPUnit)]] ([[https://github.com/Codeception/Specify|Specify]]) * [[http://codeception.com/docs/05-UnitTests|Codeception Unit Tests]] * [[http://codeception.com/docs/06-ModulesAndHelpers|Codeception Modules and Helpers]] * [[https://pceuropa.net/blog/yii2-tests-codeception-configuration-first-test|PCEuropa: Yii 2 Test Codeception Configuration]] * [[https://code.tutsplus.com/tutorials/programming-with-yii2-automated-testing-with-codeception--cms-26790|EnvatoTuts+: Yii2 Automated Testing with Codeception]] * [[https://www.toptal.com/php/php-testing-with-codeception|TopTal: PHP Testing with Codeception]] * [[https://phpunit.de/manual/current/en|PHPUnit Manual]] * [[https://cucumber.io/docs/reference|Cubumber: Gherkin Reference]] * [[http://docs.behat.org/en/v2.5/guides/1.gherkin.html|Behat: Gherkin Syntax]] * [[http://docs.behat.org/en/v2.5/quick_intro.html|Behat: Quick Intro]] * [[http://mkdocs-docs.readthedocs.io/en/latest/codeception|Codeception with Faker and Fixtures]] * [[http://www.yiiframework.com/doc-2.0/guide-test-fixtures.html|Yii 2 Fixtures]] * [[https://github.com/fzaninotto/Faker|Faker Data Types]] * [[https://martinfowler.com/articles/mocksArentStubs.html|Mocks Aren't Stubs]] * [[https://www.w3schools.com/cssref/css_selectors.asp|CSS Selectors]]