Yii2 Codeception invalid routing? - php

I am trying to set-up acceptance tests using Codeception in Yii2.
So far so good with regards to installation, but I am having a route issue.
When I do:
codeception run acceptance
I get this feedback:
1) Failed to ensure login page works in LoginCept (./acceptance/LoginCept.php)
Step I fill field "input[name="LoginForm[username]"]",""
Fail Form field by Label or CSS element with 'input[name="LoginForm[username]"]' was not found.
Scenario Steps:
3. $I->fillField("input[name="LoginForm[username]"]","")
2. // I am going to submit login form with no data
1. $I->amOnPage("/backend/web/index-test.php/")
The input with name LoginForm[username] exists on the page, but apparantly Codeception is not getting the correct page.
Should /backend/web/index-test.php also have the approot path in it? When I request approot/backend/web/index-test.php in the browser it all works fine.
Thanks for any pointers.
Alex
UPDATE: hereby the contents of 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
- tests\codeception\common\_support\FixtureHelper
# 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
config:
PhpBrowser:
# PLEASE ADJUST IT TO THE ACTUAL ENTRY POINT WITHOUT PATH INFO
url: http://localhost:8080
# WebDriver:
# url: http://localhost:8080
# browser: firefox
# restart: true
UPDATE BASED ON COMMENTS BELOW:
I'm lost. I tried hardcoding the path, and even tried hardcoding the localhost URL, but then I get this response:
$I->amOnPage("/backend/web/index-test.php/localhost/www/yii2KickDish/backend/web")
which clearly is a bogus URL....so how can I get Codeception to resolve to the right location?

I got the same issue.
Seems like the "amOnPage" method build into the AcceptanceTesterActions trait isnt working well with yii2 url pattern.
That's how i solved this.
Create a class MainTester extending from AcceptanceTester
namespace tests\codeception\master\Step\Acceptance;
use tests\codeception\master\AcceptanceTester;
class MainTester extends AcceptanceTester
{
public function amOnPage($url)
{
$page = \Yii::$app->getUrlManager()->createUrl($url);
return parent::amOnPage($page);
}
}
Then in my Cest class
use tests\codeception\master\Step\Acceptance\MainTester as AcceptanceTester;
class TestClassCest
{
public function testMethod(AcceptanceTester $I)
{
$I->amOnPage('/example/something');
}
}

Related

Symfony5 / Codeception: Service is not available in container

TL;DR
In Codeception test I am trying to $I->grabService(). Service works in controllers and has no custom config, but I get:
Fail Service App\Service\Car is not available in container
Full Story
I have a project with some Services which are basically classes, which do some processing. All the Services are accessible via service container. I am testing each class in functional suite (and some in unit) and everything worked fine till today.
So today I was adding a new Service and of course a test. I did:
root#9c80b567f681:/var/www/html# vendor/bin/codecept g:cest functional Service/Car
Test was created in /var/www/html/tests/functional/Service/CarCest.php
Test looks like this:
<?php
namespace App\Tests\Service;
use App\Service\Car;
use App\Tests\FunctionalTester;
class CarCest
{
public function _before(FunctionalTester $I)
{
$I->grabService(Car::class);
}
public function tryToTest(FunctionalTester $I)
{
}
}
Now I manually in PhpStorm create a new class. Class looks like this:
<?php
namespace App\Service;
class Car
{
}
This is output of my testing:
root#9c80b567f681:/var/www/html# vendor/bin/codecept run tests/functional/Service/CarCest.php
Codeception PHP Testing Framework v4.1.6
Powered by PHPUnit 9.2.6 by Sebastian Bergmann and contributors.
Running with seed:
App\Tests.functional Tests (1) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
✖ CarCest: Try to test (0.00s)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Time: 00:01.616, Memory: 34.00 MB
There was 1 failure:
---------
1) CarCest: Try to test
Test tests/functional/Service/CarCest.php:tryToTest
Step Grab service "App\Service\Car"
Fail Service App\Service\Car is not available in container
Scenario Steps:
1. $I->grabService("App\Service\Car") at tests/functional/Service/CarCest.php:12
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
TL;DR
Fail Service App\Service\Car is not available in container
Now most of other tests I have use the same concept: I get service in _before() and then test it. Everything passes except of any class I add today :) WTF?!?
BTW: If I replace $I->grabService(Car::class); with any other service created before, it works fine.
My services.yaml is the standard, out-of-the-box Symfony version. I always relied simply on the fact everything in src/* is already a service.
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
I spent the whole morning installing/reinstalling/restarting PC... I am completely lost and stupid. Anybody has any idea ?
EDIT:
I noticed something very interesting. If I manually add the service to services.yml and set public: true, then I can use it from Codeception. But note, that I don't have to do this for any other services I created before.
TL;DR
Problem seems to be that Symfony removes all unused services upon container compilation. You can see the code here on symfony project git page.
After I noticed, that my service works correctly when it's explicitly set to public, I started digging around that and I stumbled across git issue, where someone had the same problem. Some more digging (and talking to people smarten than me) got me to the link posted on top of this answer.
BOOM! Only took like 4 days...

Set user agent in codeception cept

I have some features / limitations in project for search bot crawlers and I would like to test them using codeception and I found there is an option to set headers using setHeader method. In my case it doesn't work though. I'm getting [RuntimeException] Call to undefined method FunctionalTester::setHeader error.
Code bellow:
<?php
/** #var \Codeception\Scenario $scenario */
$I = new FunctionalTester($scenario);
$I->setHeader('User-Agent', 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)');
FileDetailPage::of($I)->amOnFileDetail(96972088, 'raped.zip');
According to Authorization header not making it through in Codeception API testing I wonder if I have to set user agent header for every request. Isn't there method or configuration option to change User-Agent permanently?
You might be confusing Functional tests with Acceptance tests. Only Acceptance tests use PhpBrowser. Functional tests run the PHP code directly without making HTTP requests.
But to try to answer the question of setting headers globally...
Based on my understanding of the doc page here, I think you're supposed to be able to set the user agent in your config file, like this:
class_name: AcceptanceTester
modules:
enabled:
- PhpBrowser:
url: http://ames.dev/
headers:
User-Agent: Codeception Custom User Agent
- \Helper\Acceptance
But I haven't got this fully working yet. Please assist by editing my post if you'd like to correct it.
In my case, I made it work like this:
class_name: AcceptanceTester
modules:
enabled:
- PhpBrowser:
url: http://xxxx.xx
headers:
header-testing:
value: someValue
- \Helper\Acceptance
This makes array value in the $_SERVER variable:
HTTP_HEADER_TESTING => "someValue"

How to use Codeception to test laravel 5 APIs?

I am new to Codeception and I am trying to test my web service which I have built using Laravel 5. I am following the guide here.
So I created a suite called api first:
codecept generate:suite api
api.suite.yml
class_name: ApiTester
modules:
enabled:
- REST:
url: http://localhost:8000/api/
depends: Laravel5
AuthenticateUserCept.php
<?php
$I = new ApiTester($scenario);
$I->wantTo('authenticate a user');
$I->haveHttpHeader('Content-Type', 'application/x-www-form-urlencoded');
$I->sendPOST('/authenticate', [
'username' => 'archive',
'email' => 'admin#admin.com',
'password' => 'password'
]);
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
I have already tried this using Postman and it runs fine. The /authenticate route takes the 3 parameters and spits out a JWT token happily but I cannot make it work with Codeception.
1) Failed to authenticate a user in AuthenticateUserCept (tests/api//AuthenticateUserCept.php)
Step I see response code is 200
Fail Failed asserting that 500 matches expected 200.
Scenario Steps:
3. $I->seeResponseCodeIs(200)
2. $I->sendPOST("/authenticate",{"username":"archive","email":"admin#admin.com","password":"password"})
1. $I->haveHttpHeader("Content-Type","application/x-www-form-urlencoded")
Where am I going wrong? And moreover it is still blurry to me that how do I refactor this particular test in order to have a JWT for other requests that I make. Any help is appreciated.
EDIT changes in api.suite.yml
class_name: ApiTester
modules:
enabled:
- REST:
url: http://localhost:8000/api/
depends: Laravel5
config:
Laravel5:
environment_file: .env.testing
Before I answer, some comments:
on the enabled line in api.suite.yml I include: enabled: [PhpBrowser, REST, ApiHelper, Asserts].
The url in the REST line doesn't make any difference for the API suite, it's usually only used for the acceptance suite. This should be doing everything via the routes directly into your code instead of using phpbrowser mode or similar where it's making HTTP calls.
Instead of using the PhpBrowser mode you can use the Laravel5 mode -- the former will send requests through the middleware, the Laravel5 mode won't put requests through the middleware if the APP_ENV is set to testing.
In terms of how to fix this, I would start by trying to figure out what the code is doing to spit out the 500 error. One thing that you might do is try to insert some debug/log lines in your code. Another thing is to put in a line like this into the test case:
$I->seeResponseContains('blablabla');
Of course that will fail, but it will produce an output in the tests/_output directory so that you can see exactly what response your code gave back along with the 500 error (of course you need to do this the step before testing for the 200 condition, otherwise your test will bail out at that step).
One common cause of this type of error in test cases is validation applied to a custom Request class that's passed in to your controller method. If that validation fails then the controller method doesn't get hit and you will get a 500 error without being able to debug it in the controller. I consider custom Request classes of this type to be a bad idea.
Another possible line of enquiry is to build a functional test that tests the same route. Functional tests via the Laravel5 helper module are usually easier to get working and get debugged. Once you have a basic functional test in place and resolve any problems that you hit getting that going then it's easier to get the API tests working.

Clearing cache for laravel 4 app before codeception tests

I'm using codeception to run acceptance tests for a laravel app. One problem I've been into is is that my login tests start failing when the login page gets cached and, I guess, I'm getting logged in automatically. I think this is the case because my tests start passing again when I clear the cache and they'll generally start failing without my having changed the tests or the application code at all.
Here's the login test in question, now extracted into a helper method
public function login($username, $password, $I) {
$I->amOnPage('users/login');
$I->fillField('email', $username);
$I->fillField('password', $password);
$I->click('Login');
$I->amOnPage('admin/');
$I->see('Welcome');
}
I've been clearing the cache periodically whenever the tests fail but it's becoming tedious. I'd like to be able to register a helper to clear the cache for me and just call the function in all my tests. I extracted the function into a helper as suggested here with the following function in AcceptanceHelper.php:
public function clearCache($I) {
$cache = $this->getModule('Cache');
$cache->flush();
}
This seems to be what was suggested by the documentation here, but I get the error "Module Cache couldn't be connected". It looked like I needed the Laravel4 module so I added that to my acceptance.suite.yml file but no luck there either I received this error:
SQLSTATE[28000] [1045] Access denied for user 'stephen'#'localhost' (using password: NO)
I thought I'd need to authorize mysql in the config file, but that didn't seem to work either. Here's my acceptance.suite.yml file:
class_name: AcceptanceTester
modules:
enabled:
- PhpBrowser
- AcceptanceHelper
- Laravel4
config:
PhpBrowser:
url: 'http://104.131.29.69:8000/'
Laravel4:
user: 'root'
password: 'pAsSwOrD'
Finally I read this answer and it seemed as though I shouldn't actually have included Laravel4 in the config file and that my helper function should look more like this:
public function clearCache($I) {
$L = $I->getLaravel4();
Cache::flush();
}
But I just wind up getting this error instead:
Class 'Codeception\Module\Cache' not found
So I'm stuck. Thanks!
Artisan::call('cache:clear');
is better approach.
Ok so I figured out how to do this. Apparently there's a module called cli which you can include in the acceptance.suite.yml file like this:
class_name: AcceptanceTester
modules:
enabled:
- PhpBrowser
- AcceptanceHelper
- Cli
This module allows you to use shell commands in your script with the runShellCommand() function. Since my cache is stored in files in the app/storage/cache/ directory the shell command I need to execute is:
rm app/storage/cache/*
and voilá cache cleared. Then in the test file it will look like this:
$I->runShellCommand('rm -rf app/storage/cache/*');
I decided to simplify this a bit by including it in the function I used to login, since I knew I'd want to clear the cache before each login I just included that line in a login function inside AcceptanceHelper, which then is accessible in all of my tests.
This is a bit of workaround since it isn't agnostic to the kind of caching I'm using (if I were to use memcached I would need to do something different) but it worked for me so I thought I'd share it.

Behat with Mink

Does anyone know how to successfully configure Mink to work with Behat? In case if anyone doesn't know, Behat is a BDD(Behaviour-Driven Development) framework for PHP and Mink provides a browser emulators abstraction layer to test with.
You can find out more about Behat at http://behat.org/ and Mink at https://github.com/Behat/Mink or http://www.knplabs.com/fr/blog/one-mink-to-rule-them-all
Basically i followed the instructions at http://www.knplabs.com/fr/blog/one-mink-to-rule-them-all to configure my Mink to work with my Behat. My behat.yml, the one located inside the Behat folder, not the Mink folder, is as follows:
default:
paths:
features: %%BEHAT_CONFIG_PATH%%/features
formatter:
name: progress
pretty:
formatter:
name: pretty
parameters:
multiline_arguments: false
default:
environment:
parameters:
start_url: http://localhost/
imports:
- mink/behat.yml
I also have the following code in my features/support/boostrap.php
require_once 'mink/autoload.php';
However, having the following code in my features/support/env.php
$world->client = new \Goutte\Client;
would give me a PHP Fatal error: Class 'Goutte\Client' not found in terminal(OSX) when i use the behat command. This happens even if i have the goutte.phar inside my behat/Mink/Vendor/Goutte folder.
Hope anyone can enlighten me on where i went wrong and if there was any part in the question where I wasn't being clear about it, do let me know. Thanks a lot.
Here it is: https://github.com/knplabs/mink-demo ;-)
Basically, with Mink, you don't need to create or require Goutte client it's done by Mink automatically. Your $world was also enhanced and now you're able to get mink session inside step definitions:
$downloadsLink = $world->getSession()->getPage()->findLink('downloads');
Also, you've forgot to include PHPUnit!
See mink-demo for getting great example ;-)

Categories