Mocking a function using Laravel and PHPUnit - database

I am using Laravel for some projects, and I am trying to create a test for a function. Here is the function in the my controller:
public function showThoseThings()
{
return parent::httpRequest(
function ($context) {
$context->results['item'] = $this->object->findOrFail(list_id)->getElements;
}
);
}
the "getElements()" function is defined in the model as follow:
public function getElements()
{
return $this->belongsToMany(
'things',
'thing_to_list',
'list_id',
'thing_id'
)
->withPivot('position')
->orderBy('thing_to_list.position');
}
Basically in the backend there are three tables a list table that is a collection of things, then there is a things table that contains multiple things associated to a list then we have the pivot table. How can I mock this with Laravel. Here is what I have been doing:
public function testShowThoseTHingsSuccess()
{
$this->mock->shouldReceive('findOrFail')->with(1)->andReturn($this->mock);
$this->mock->shouldReceive('getElements')->once()->andReturn($this->mock);
$response = $this->call('GET', 'workingURI');
var_dump($response);
$this->assertTrue($response->isOk());
But when running phpunit in the command line I am getting:
"Unknown Error","message":"SQLSTATE[HY000] [1045] Access denied for user... Fails asserting that false is true".

I don't know what your $this->mock->.. method is doing but apparently not mocking the model you want.
Because the error states that something tries to access the database and doesn't have correct credentials.
Also the lines:
$this->mock->shouldReceive('findOrFail')->with(1)->andReturn($this->mock);
$this->mock->shouldReceive('getElements')->once()->andReturn($this->mock);
Makes no sense to me, You create a mock of a model and when the methods findOrFail or getElements are triggered you return the same object.. The method getElements states to me that there should be returned some array with models..
Some more info when you want to use a testing database
When you run unit test with laravel and uses the base test class, then Laravel sets the environment to testing. Make sure that you have the right database configuration set for this environment.
You can use I.E. the following configuration to create a database in memory for testing:
// app/config/testing/database.php
<?php
return array(
'default' => 'sqlite',
'connections' => array(
'sqlite' => array(
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => ''
),
)
);
Don't forget to migrate your DB schema before testing, place the following line in the public function setUp() of the base test class.
Artisan::call('migrate');
More info about simulating the databse: NetTuts testing laravel models

Related

What's going wrong with my migrations on CakePHP 4?

I've applied a couple of minor changes to the database structure for my app, adding new columns to a table called Plots. This is one of the migrations -
declare(strict_types=1);
use Migrations\AbstractMigration;
class AddGarageToPlots extends AbstractMigration
{
public function change()
{
$table = $this->table('plots');
$table->addColumn('garage', 'string', [
'default' => null,
'limit' => 255,
'null' => true,
]);
$table->update();
}
}
When I apply the migration it seems to run fine: there are no errors and I can see the new column in the database if I connect directly to it but when I try to access data in the new field in a view using, for example, <?= $plot->garage ?> it consistently returns null even though I have populated this field via the direct connection.
Is there something else I need to do that I'm missing here or is there some way I can check that the migration has worked properly like a schema file somewhere?
Found the answer to my own question by reading slightly further in the documentation - migrations and deployment.
I needed to run bin/cake schema_cache clear

setTable not working as expected with a legacy database in CakePHP 3.x

I have a legacy (not written to Cake naming conventions) database on a CakePHP 3.5.13 application.
One of the database tables is named article_95.
When I attempted to bake the application it's showing the entity name as Article95. It's then producing loads of error messages saying:
Table 'article95' doesn't exist
So I've read CakePHP error: cake bake is using the wrong table name and How to use table name different then in database in cake php 3 and decided to do it manually using setTable().
So I have created src/Model/Table/Article95Table.php with the following code in it:
namespace App\Model\Table;
use Cake\ORM\Table;
class Article95Table extends Table
{
public function initialize(array $config)
{
$this->setTable('article_95');
}
}
But it won't seem to recgonise this. In a controller I've created a testing method and done the following:
Cache::clear(false);
$Article95 = TableRegistry::get('Article95');
debug($Article95->find()->list());
But it's coming up with:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'article95' doesn't exist
It's as if it's not reading the setTable() method.
I've used the Cache::clear(false) to ensure it's not being cached and have inspected the tmp/cache directory for any suspect files - nothing relevant in there.
Any ideas?
Equally if I change my debug() statement to just debug($Article95) I get the following, with the wrong table name:
object(Cake\ORM\Table) {
'registryAlias' => 'Article95',
'table' => 'article95',
'alias' => 'Article95',
'entityClass' => '\Cake\ORM\Entity',
'associations' => [],
'behaviors' => [],
'defaultConnection' => 'default',
'connectionName' => 'default'
}
Incidentally, if I put a non-existent class/entity name in TableRegistry::get() - for example TableRegistry::get('dsfdsfdsfsd'); it won't produce an error message but will show the object above with the non-existent table name. Surely this is also wrong?
I've managed to get this working so wanted to share my solution.
After some trial and error it seems that appending an 's' to the class name (presumably to make Cake consider it plural?) works.
So I renamed the Table class to Article95sTable.php and then used the following code inside:
class Article95sTable extends Table
{
public function initialize(array $config)
{
$this->setTable('article_95');
}
}
Now when I call the following it picks up the table correctly:
$foo = TableRegistry::get('Article95s');
debug($foo);
To confirm this is working if I rename the table inside my setTable() method the output will change accordingly.

CakePHP 3.5 Auth use multiple tables

I have an Auth process which works fine with one userModel. But not only because of my DB schema I need to have one login method/action which works with multiple models.
So far I've tried everything I was able to think of or find online - for example editing this Cake 1.3 solution into Cake 3 and a few more hints I was able to find.
However, I'm not able to figure it out.
Thank you for any answer.
My AppController component load:
$this->loadComponent('ExtendedAuth', [
'authenticate' => [
'Form' => [
//'userModel' => 'Admins',
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Admins',
'action' => 'login'
],
// If unauthorized, return them to page they were just on
'unauthorizedRedirect' => $this->referer(),
]);
My ExtendedAuthComponent:
class ExtendedAuthComponent extends AuthComponent
{
function identify($user = null, $conditions = null) {
$models = array('Admins', 'Users');
foreach ($models as $model) {
//$this->userModel = $model; // switch model
parent::setConfig('authenticate', [
AuthComponent::ALL => [
'userModel' => $model
]
]);
$result = parent::identify(); // let cake do its thing
if ($result) {
return $result; // login success
}
}
return null; // login failure
}
}
EDIT1: Description of situation
I have two separate tables (Admins, Users). I need just one login action which tries to use Admins table prior to Users. Because of the application logic I can't combine them to one table with something like 'is_admin' flag. So basically what I need is instead of one specific userModel set in Auth config, I need a set of models. Sounds simple and yet I'm not able to achieve it.
EDIT2: Chosen solution
Based on the answer below, I decided to update my schema. Auth users table is just simplified table with login credentials and role and other role-specific fields are then in separate tables which are used as a connection for other role-specific tables. Even though the answer is not exactly a solution for the asked question, it made me think more about any possible changes of the schema and I found this solution because of it so I'm marking it as a solution. I appreciate all comments as well.
As Mark already said in a comment: Don't use two users tables. Add a type field or role or whatever else and associated data in separate tables if it's different like admin_profiles and user_profiles.
Don't extend the Auth component. I wouldn't recommend to use it anymore any way because it's going to get deprecated in the upcoming 3.7 / 4.0 release. Use the new official authentication and authorization plugins instead.
If you insist on the rocky path and want to make your life harder, well go for it but then you should still not extend the auth component but instead write a custom authentication adapter. This is the right place to implement your custom 2-table-weirdness. Read this section of the manual on how to do it.

CakePHP "with" method in mock object don't Work

I'm trying testing my application using CakePHP 2.2 RC1, in the certain action of my controller i need one information of Auth object, in my test i have created an mock object for the Auth component, but when i call the method with my mock object become invalid, when i don't put this everything works fine.
Below the mock object wich dont work
$this->controller->Auth
->staticExpects($this->any())
->method('user')
->with('count_id')
->will($this->returnValue(9));
Thanks for your attention guys.
--
Edit
Above the full code of my test case, this a very simple test.
class TagsControllerTest extends ControllerTestCase {
public function testView(){
$Tags = $this->generate('Tags', array(
'components' => array(
'Session',
'Auth' => array('user')
)
));
$Tags->Auth->staticExpects($this->any())
->method('user')
->with('count_id')
->will($this->returnValue(2));
$result = $this->testAction('/tags/view');
$this->assertEquals($result, 2);
}
}
And the code of my action in the Tag controller, this don't have nothing more (for testing purposes) them a return of user object with count_id as parameter.
public function view(){
return $this->Auth->user('count_id');
}
Running the test I received this message:
Expectation failed for method name is equal to when invoked zero or more times
Parameter 0 for invocation AuthComponent::user(null) does not match expected value.
Failed asserting that null matches expected 'count_id'.
After looking at the AuthComponent code, I think the problem lies in the fact that you're not mocking the entire component or you're not mocking the _getUser() method. Don't forget: the methods you're not mocking are real methods!
If you look at the code you'll see that user() is called by _getUser() which in turn is called by startup().
There are two ways to solve this problem, the first is to mock the entire AuthComponent:
$Tags = $this->generate('Tags', array(
'components' => array(
'Session',
'Auth' /* no methods */
)
));
or mock _getUser() in addition to user():
$Tags = $this->generate('Tags', array(
'components' => array(
'Session',
'Auth' => array('user', '_getUser')
)
));
Hopefully, this should solve your problem.
I was facing the same issue which I could not solve by the solution provided.
The solution is to use staticExpects() instead of expects() as user is a static function.
$batches->Auth->staticExpects($this->once())->method('user')
->with('id')
->will($this->returnValue(1));

save() returning false, but with no error in CakePHP

My debug value is set to 2, and it's displaying all the queries, except the one I need.
I have an Items controller method that is calling this method in the User model (Item belongsTo User):
function add_basic($email, $password) {
$this->create();
$this->set(array(
'email' => $email,
'password' => $password
));
if($this->save()) {
return $this->id;
}
else {
return false;
}
}
I have confirmed that $email and $password are being passed into the function correctly (and are populated with legit data). email and password are the names of the fields in the User model.
I have also confirmed that on $this->save() it is returning false, but when I view the page where this occurs, the query is not being printed in the debug, and there is no error being thrown, so I have no idea whats going wrong.
Any ideas on how I can see the error, or why the query doesn't seem to be getting executed?
It's weird, cause right after this, I have another model saving data to it in the exact same fashion, it goes off without a hitch.
This will probably give you the info you need (assuming it's not saving because of invalid data, of course):
if(!$this->save()){
debug($this->validationErrors); die();
}
Have you got a beforeValidate() or beforeSave() method in the model or app model? Ifso, are they returning true? Failing that, use a debugger, set a break point in your IDE at the top of cake/libs/models/model.php save() method and step through the code until it returns false. Failing that add die('here'); calls.
Try this:
if ($this->save()) {
return $this->id;
}
else {
var_dump($this->invalidFields());
return false;
}
#cakePHP 3.6 and above: By default, the request data will be validated before it is converted into entities. If any validation rules fail, the returned entity will contain errors. It can be read by getErrors() method.
The fields with errors will not be present in the returned entity:
Say, you have an entity
use App\Model\Entity\Article;
$entity = $this->ModelName->newEntity([
'id' => 1,
'title' => 'New Article',
'created' => new DateTime('now')
]);
$result = $this->ModelName->save($entity);
\Cake\Log\Log::debug($entity->getErrors());
If you’d like to disable validation when converting request data, set the validate option to false:
$article = $articles->newEntity(
$this->request->getData(),
['validate' => false]
);
Ref: https://book.cakephp.org/3/en/orm/validation.html
Make sure to check your tables:
Does ID have auto increment enabled?
Is id your primary key?
the auto_increment issues killed me.
Easy way to check: if any of your rows have ID = 0, auto_increment is likely disabled.
CakePHP 3.6
$entity = $this->Model->newEntity([
'account_id' => $id,
'gallery_id' => $gallery_id
]);
$result = $this->Model->save($entity);
print_r($entity->getErrors());
The other situation where CakePHP fails to report any $this->Model->validationErrors and no other errors is potentially when $this->request->data isn't as Cake expects and is simply ignoring your data, not saving, no validation errors. For example if your data was provided by DataTables you might see this format $this->request->data[0]['Model']['some_field'].
$this->Model->save($this->request->data[0]) will work however.

Resources