I am building an API in Yii2 2.0.14 and running tests with Codeception. Examples in the tutorial* show that I can call fixtures like so:
$profile = $I->grabFixture('profiles', 'user1');
However this doesn't seem to be available in my test class here:
<?php
namespace frontend\tests\api;
use frontend\tests\ApiTester;
class DemoCest
{
public function _fixtures()
{
return [
'users' => [
'class' => UserFixture::className(),
// fixture data located in tests/_data/user.php
'dataFile' => codecept_data_dir() . 'user.php'
],
];
}
public function demo(ApiTester $I)
{
$users = $I->grabFixture('users');
$I->wantTo('perform actions and see result');
$I->haveHttpHeader('Content-Type', 'application/x-www-form-urlencoded');
$I->sendPOST('/user/test', ['name' => 'davert', 'email' => 'davert#codeception.com']);
$I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
$I->seeResponseIsJson();
$I->seeResponseContainsJson(['message' => 'test OK']);
}
}
I have seen that you need to add fixtures to frontend/tests/api-suite.yml but Codeception is throwing an exception when I do that
PHP Notice: Undefined index: tests in
/var/www/vendor/codeception/base/src/Codeception/Command/Run.php on
line 389
I am completely lost.
actor: ApiTester
modules:
enabled:
- Yii2:
part: [orm, email, fixtures]
- \frontend\tests\Helper\Api
- REST:
url: http://securedata.test/api/v1
depends: PhpBrowser
part: Json
Can anyone lead me in the right direction?
tutorial - http://www.yiiframework.com/doc-2.0/guide-test-fixtures.html
Add to your suite configuration, you haven't defined the tests path in the codeception.yml under paths.
If you look into the line the error points to it will take you to the line inside the matchSingleTest function
in your case
list(, $suite, $test) = $this->matchTestFromFilename($suite, $config['paths']['tests']);
in which it gives tests as undefined index for the $config['paths'] array, which if you backtrack parses the codeception.yml to the $config,
You are using an older version of Yii 2.0.14 which may have the missing section from the execute() see this ISSUE
So add tests directory as . and make sure your codeception.yml is in the /tests root.
see the following
paths:
tests: .
Related
So I'm getting into Unit Testing and I have created a test for creating and adjustment line in my DB. Here is the code:
$response = $this->json('POST', '/quotes/3/adjustment',
[
'adjustments' => array([
'description' => 'TEST-Description',
'amount' => 1000,
'quote_id' => 3
])
]
);
$response->assertStatus(201);
When creating it hits my controller then an instance of my Adjustment model is created, and in that model I have this code for the creating of it:
foreach($request->adjustments as $adjustment) {
if(array_key_exists('id', $adjustment)) {
$this->find($adjustment['id'])->update([
'description' => $adjustment['description'],
'amount' => $adjustment['amount'],
'quote_id' => $quote->id
]);
} else {
$this->create([
'description' => $adjustment['description'],
'amount' => $adjustment['amount'],
'quote_id' => $quote->id,
]);
}
}
return $quote;
So it expects adjustments to be an array and I thought I had it coded properly in the test but i get back a 200 response, which is not the 201 as expected. Any ideas on how to properly pass the single array in my test file so that it passes the test?
Here is my controller:
$adjustment = new Adjustment();
return $adjustment->newAdjustment($quote, $request)->adjustments;
On a side note if I run this in postman as raw JSON(applicatoin/json) it creates the resource in the DB:
{
"adjustments": [{
"description": "testing-postman",
"amount": 1000,
"quote_id": 1
}
]
}
As of Laravel 5.6, if you return a newly created model from your controller, Laravel will automatically set the response status to a 201. This typically is what you would do when building an API that follows RESTful practices.
However that may not suit your case as you may need to return other data from your controller and not just the newly created model and if so, I believe Laravel will return a 200 instead.
So you have a few of options:
In your controller you could force the 201 with return response($myData, 201);
Return only the newly created model and nothing else.
Or just have your test do the following:
$response->assertStatus(200);
$this->assertDatabaseHas('adjustments', $adjustment->toArray());
With the third option, your test is verifying that everything went okay and that the actual model was created and exists in the database (you'll need to adjust it based on your needs).
More info on the Laravel 5.6 201 response: https://laravel.com/docs/5.6/upgrade - Search on the page for: Returning Newly Created Models
Any one know how to send error messages to database in laravel which generate from app/exceptions/handler.php ?
I need to send what error massages generated in report() method to database.
If you are interested doing this manually, you can do something as following.
Step 1 -
Create a model to store errors that has a DB structure as following.
class Error extends Model
{
protected $fillable = ['user_id' , 'code' , 'file' , 'line' , 'message' , 'trace' ];
}
Step 2
Locate the App/Exceptions/Handler.php file, include Auth, and the Error model you created. and replace the report function with the following code.
public function report(Exception $exception) {
// Checks if a user has logged in to the system, so the error will be recorded with the user id
$userId = 0;
if (Auth::user()) {
$userId = Auth::user()->id;
}
$data = array(
'user_id' => $userId,
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'message' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
);
Error::create($data);
parent::report($exception);
}
(I am demonstrating this using laravel 5.6)
Because Laravel uses Monolog for handling logging it seems that writing Monolog Handler would be the cleanest way.
I was able to find something that exists already, please have a look at monolog-mysql package. I did not use it, so I don't know whether it works and if it works well, but it's definitely good starting point.
I spent half day to resolve this issue with no success.
I'm doing a setup in EC2, centos 6/64 bit. LAMP installed. On another hosting, my zf2 solution it work fine, so I've searched issue in php modules installed also (list at the end).
This error happen when zf2 try to get an instance of my custom service, also with wasabi mail.
\zend\config\application.config.php
'config_glob_paths' => array(
'./config/autoload/{,*.}{global,local}.php',
),
\zend\config\autoload\global.php
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
'Zend\CustomLogger' => function ($sm) {
$auth = $sm->get('zfcuser_auth_service');
$customLogger = new \Application\Service\CustomLogger(
$sm->get('Request'),
$sm->get('ZendLog'),
new \Zend\Session\SessionManager(),
$auth->getIdentity(), // $user
$sm->get('Mail'));
return $customLogger;
},
controller
<?php
namespace Foo\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Doctrine\ORM\EntityManager;
use MyProject\Proxies\__CG__\OtherProject\Proxies\__CG__\stdClass;
class FooController extends AbstractActionController
{
protected $customLogger;
private function getCustomLogger()
{
if (null === $this->customLogger) {
$this->customLogger = $this->getServiceLocator()->get('Zend\CustomLogger');
}
return $this->customLogger;
}
public function indexAction()
{
$this->getCustomLogger();
$this->customLogger->controllerLog("ENTER IN Foo\Controller\FooController\index", "info");
// .... other code
}
}
Error
Zend\ServiceManager\Exception\ServiceNotCreatedException
File:
/var/www/solutions/mysolution/zend/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:930
Message:
An exception was raised while creating "Zend\CustomLogger"; no instance returned
PHP modules installed
bz2.so curl.so fileinfo.so iconv.so mbstring.so mysqlnd.so pdo_sqlite.so shmop.so sqlite3.so sysvshm.so xmlreader.so xsl.so
calendar.so dom.so ftp.so intl.so mysqlnd_mysqli.so pdo_mysqlnd.so phar.so simplexml.so sysvmsg.so tokenizer.so xml.so zip.so
ctype.so exif.so gettext.so json.so mysqlnd_mysql.so pdo.so posix.so sockets.so sysvsem.so wddx.so xmlwriter.so
Extension enabled in PHP ini
extension=/usr/lib64/php/5.5/modules/php_bz2.so
extension=/usr/lib64/php/5.5/modules/php_curl.so
extension=/usr/lib64/php/5.5/modules/php_fileinfo.so
extension=/usr/lib64/php/5.5/modules/php_gd2.so
extension=/usr/lib64/php/5.5/modules/php_intl.so
extension=/usr/lib64/php/5.5/modules/php_mbstring.so
extension=/usr/lib64/php/5.5/modules/php_mysql.so
extension=/usr/lib64/php/5.5/modules/php_mysqli.so
extension=/usr/lib64/php/5.5/modules/php_openssl.so
extension=/usr/lib64/php/5.5/modules/php_pdo_mysql.so
extension=/usr/lib64/php/5.5/modules/php_soap.so
extension=/usr/lib64/php/5.5/modules/php_xmlrpc.so
extension=/usr/lib64/php/5.5/modules/php_xsl.so
So simple solution... permission on log directory and some others directory are wrong. Restore the correct permissione, user and group on directory (e.g. data/logs, data/cache...)
I have a MainController which extends Controller. All my app's controllers extend from MainController which includes various methods and properties which need to be accessible from any Controller.
Within my MainController is beforeAction, which does several things:
Checks for redirects held in the database and performs them if the URL matches one in the DB.
Generates <head> data for each controller
Gets the language and country the user is looking at based on cookie and slug of the URL. (i.e. http://example.com/netherlands).
Will render a generic page from a template if URL matches one from the database's pages table.
It's the last that I am struggling with. In my MainController I have this:
/**
* Before action, check all $this->before_actions satisfy. If no head_data provided, try and fill in some basics
*/
public function beforeAction( $action )
{
// Run parent method first
if (!parent::beforeAction($action))
return false;
// Check redirects
$this->checkRedirects();
if( $this->checkPages() )
{
// If not error page, loop through before methods
if( $action->id !== 'error' )
{
// Loop through actions to peform and do them
foreach ( $this->before_actions as $before_method )
$this->$before_method();
}
return true;
}
}
Where $this->checkPages() contains the following:
/**
* Check for pages
*/
public function checkPages()
{
// Attempt to find page for this request
$page = Page::find()->where( [ 'permalink' => trim( str_replace( getBaseUrl() , "", getCurrentUrl() ), "/" ) ] )->one();
// If found, load it instead
if( !empty( $page ) )
return Yii::$app->runAction( "pages/show", [ 'id' => $page->id ] );
// Else, return
return true;
}
The issue I am having is that if I go to http://example.com/story, because there is no StoryController, the returns a 404 error although the action does run and the view "views/story/show" is output.
How can I prevent this?
EDIT:
To add, the log shows that it first says:
"Unable to resolve the request 'story/index'".
But then additional logs show:
"Route to run: pages/show" ... "Running action: app\controllers\PagesController::actionShow()"
..
Rendering view file: /Users/stefandunn/Documents/Local Machine Development/views/pages/show.php
So I am guessing it's the first log result causing the 404 status
Add one last route that can catch any pattern and redirect to custom action.
'urlManager' => [
'class' => 'yii\web\UrlManager',
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
//...
'<any:.*>' => 'site/index'
],
],
In a ZF2 application I have some cofigs, that: 1. need to be different dependening on the environment; 2. are specific for a concrete module. I'm curently using it like here described:
global.php & local.php
return array(
...
'modules' => array(
'Cache' => array(
'ttl' => 1, // 1 second
)
)
...
);
Module class
Module {
...
public function getServiceConfig() {
try {
return array (
'factories' => array(
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
'ttl' => $this->getConfig()['modules']['Cache']['ttl'],
...
));
},
...
)
);
}
...
}
...
}
It's working fine, but I believe, that the module specific settings should be accessed over one central place in the module -- the getConfig() method of the Module class. Like this:
class Module {
public function getConfig() {
$moduleConfig = include __DIR__ . '/config/module.config.php';
$application = $this->getApplicationSomehow(); // <-- how?
$applicationModuleConfig = $application->getConfig()['modules'][__NAMESPACE__];
$config = array_merge($moduleConfig, $applicationModuleConfig);
return $config;
}
...
public function getServiceConfig() {
try {
return array (
'factories' => array(
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
'ttl' => $serviceManager->get('Config')['modules']['Cache']['ttl'],
...
));
},
...
)
);
}
...
}
...
}
The problem is, that I don't get, how to access the global.php/local.php configs in the getConfig() of a module. How can I do it?
Every single configuration of every single loaded Module will be merged into one single config. Namely this would be:
$serviceManager->get('config');
The reason behind (global|local).config.php is merely for usage purpose. Global configuration files should always be deployed. Local configuration files however should only be deployed as distributionables, alias local.config.php.dist.
Distributionals will not be loaded, no matter where they are places. However common notion of ZF2 is to copy the distributionables into the /config/autoload-directory of the ZF2 Application and rename them to local.config.php
One example:
// YourModule/config/module.config.php
return array(
'key' => 1337
);
// YourModule/config/local.yourmodule.php.dist
return array(
'key' => 7331
);
Now when you publish / deploy your application, only module.config.php will be used. If someone wants to change the configuration of your Module, they would never touch module.config.php, as this file would constantly be overwritten when your module will be updated.
However what people can do is to copy:
YourModule/config/local.yourmodule.php.dist
to
/config/autoload/local.yourmodule.php
And change the config values inside this local configuration.
To understand:
You should always configure your module as best as possible for a LIVE-Scenario.
If you have environment-specific needs, overwrite this config using a local-config
local configs are never deployed automatically, this is a manual task needing to be done from inside the environment itself
Hope this got a little more clear
Ultimately:
configure your module for a LIVE-Scenario
On your development machine create a /config/autoload/mymodule.local.php and overwrite your ttl with it's development value
LoadOrder:
The last interesting part, which i have completely forgotten about, would be the load order of the configuration files. As all files are merged, this is important to note!
First to load is /config/application.config.php
Second to load would be each Modules /modules/{module}/config/module.config.php *
Last but not least the autoloadable files will be loaded /config/autoload/{filename}.php
asterix It is actually NOT module.config.php which is called, but rather the Module-classes configuration functions. Mainly these are:
getConfig()
getServiceConfig()
getViewHelperConfig()
ultimately everything under Zend\ModuleManager\Feature\{feature}ProviderInterface
If i understand this part of the ConfigListener correctly, then getConfig() will be called first and all of the specialiced {feature}ProviderInterfaces will overwrite the data of getConfig(), but don't take this for granted, it would need a check!
You're not supposed to access other Modules setting in your Module#getConfig(). If you rely on other configuration, that CAN ONLY BE for service purposes. Ergo you'd rely on Module#getServiceConfig() and inside the factories you do have access to the ServiceManagerand access your configs with $serviceManager->get('config');. (see Sam's comment)
The loading order of the configs is by default:
/config/application.config.php, that is the initial config file; not for module configs; here is the filename pattern for the config files to load defined ('config_glob_paths' => array('config/autoload/{,*.}{global,local}.php')).
{ModuleNamespace}\Module#getConfig() (e.g. Cache\Module#getConfig()), that by convention should load its /module/{ModuleNamespace}/config/module.config.php;
/config/autoload/global.php, that should not contain any module specific configs (see below);
/config/autoload/local.php, that contains environment specific settings also should not contain any module specific configs (see below); it should not versioned/deployed;
/config/autoload/{ModuleNamespaceLowerCased}.local.php (e.g. cache.local.php), that contains only the module AND environment specific settings and should not be versioned/;
For the Cache module above there can be following config files:
/module/Cache/config/module.config.php -- a complete set of module configs; loaded by Cache\Module#getConfig()
/module/Cache/config/cache.local.php.dist -- an example for /config/autoload/cache.local.php
/config/autoload/cache.local.php -- environment specific module configs
The setting ttl can be accessed from any place, where one has access to the Service Locator. For example in factory methods of Cache\Module#getServiceConfig()
class Module {
public function getConfig() {
$moduleConfig = include __DIR__ . '/config/module.config.php';
$application = $this->getApplicationSomehow(); // <-- how?
$applicationModuleConfig = $application->getConfig()['modules'][__NAMESPACE__];
$config = array_merge($moduleConfig, $applicationModuleConfig);
return $config;
}
...
public function getServiceConfig() {
try {
return array (
'factories' => array(
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
'ttl' => $serviceManager->get('Config')['ttl'],
...
));
},
...
)
);
}
...
}
...
}
For futher information about how configs are managed in ZF2 see Sam's answer and blog article.