CakePHP3&PHPStan: How to remove `undefined method..` model errors when run PHPStan for CakePHP3 app - phpstan

When run PHPStan for CakePHP3 app, Call to an undefined method errors always occurs
in the lines using CakePHP3 Dynamic Finders
How do I remove errors like dynamic methods?
/**
* #property \App\Model\Table\ArticlesTable $Articles
*/
class ArticlesController extends AppController
{
public function view($slug = null)
{
$article = $this->Articles->findBySlug($slug)->firstOrFail();
$this->set(compact('article'));
}
------ ----------------------------------------------------------------------------
Line Controller/ArticlesController.php
------ ----------------------------------------------------------------------------
58 Call to an undefined method App\Model\Table\ArticlesTable::findBySlug().
...

The concept of magic properties and methods is understood by PHPStan with the help of so-called class reflection extensions. See Docs for more details.

Related

Laravel 5 Cannot Mock View

I'm trying to write a really basic test for one of my controllers
/**
* THIS IS MY CONTROLLER. $this->badge is a repository
* #return \Illuminate\Http\Response
*/
public function index()
{
return view('badges.index')->with([
'badges' => $badges = $this->badge->all()
]);
}
I'm using repositories which return Eloquent collections. My basic test is as follows:
public function testItShowsAllBadges()
{
// Arrange
//DISABLE AUTH MIDDLEWARE ON THIS ROUTE
$this->withoutMiddleware();
// MOCK THE REPO
$this->badge->shouldReceive('all')->andReturn(new Illuminate\Support\Collection);
// Act
$response = $this->action('GET', 'BadgeController#index');
// Assert
$this->assertResponseOk();
$this->assertInstanceOf('Illuminate\Support\Collection', $response->original->getData()['badges']);
$this->assertViewHas('badges');
}
This test fails with a message 'trying to get property of non-object'. This is because I do Auth::user()->something in the view.
So I need to mock the view but I don't know how. Can someone advise?
Other SO answers do not seem to work and just result in Exceptions being thrown in the test about methods not existing on the Mock. I have tried for example:
View::shouldReceive('make')
->once()
->andReturn(\Mockery::self())
Adding this before I call the route results in a 500 error 'Method Mockery_1_Illuminate_View_Factory::with() does not exist on this mock object'. I tried adding in
->shouldReceive('with')
->once()
->andReturn(\Mockery::self());
However this results in an Exception stating that getData() does not exist on this Mock Object. Even removing that assertion, assertViewHas('badges') fails saying the response was not a view.
Also I haven't understood if View::shouldReceive... is part of Arrange or Assert phase of the test?My understanding it is part of the arrange and should go before the $this->action(....)

Issue testing Laravel Controller with Mockery | trying to get property of non-object

I'm very new to testing controllers and I'm running into a problem with a method(). I believe I'm either missing something in my test or my Controller / Repository is designed incorrectly.
The application I'm writing is basically one of those secure "one time" tools. Where you create a note, the system provides you with a URL, once that url is retrieved the note is deleted. I actually have the application written but I am going back to write tests for practice (I know that's backwards).
My Controller:
use OneTimeNote\Repositories\NoteRepositoryInterface as Note;
class NoteController extends \Controller {
protected $note;
public function __construct(Note $note)
{
$this->note = $note;
}
public function getNote($url_id, $key)
{
$note = $this->note->find($url_id, $key);
if (!$note) {
return \Response::json(array('message' => 'Note not found'), 404);
}
$this->note->delete($note->id);
return \Response::json($note);
}
...
I've injected my Note interface in to my controller and all is well.
My Test
use \Mockery as M;
class OneTimeNoteTest extends TestCase {
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('OneTimeNote\Repositories\EloquentNoteRepository');
}
public function mock($class)
{
$mock = M::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function testShouldReturnNoteObj()
{
// Should Return Note
$this->mock->shouldReceive('find')->once()->andReturn('test');
$note = $this->call('GET', '/note/1234567890abcdefg/1234567890abcdefg');
$this->assertEquals('test', $note->getContent());
}
}
...
The error I'm getting
1) OneTimeNoteTest::testShouldReturnNoteObj
ErrorException: Trying to get property of non-object
/Users/andrew/laravel/app/OneTimeNote/Controllers/NoteController.php:24
Line 24 is in reference to this line found in my controller:
$this->note->delete($note->id);
Basically my abstracted repository method delete() obviously can't find $note->id because it really doesn't exist in the testing environment. Should I create a Note within the test and try to actually deleting it? Or would that be something that should be a model test? As you can see I need help, thanks!
----- Update -----
I tried to stub the repository to return a Note object as Dave Marshall mentioned in his answer, however I'm now receiving another error.
1) OneTimeNoteTest::testShouldReturnNoteObj
BadMethodCallException: Method Mockery_0_OneTimeNote_Repositories_EloquentNoteRepository::delete() does not exist on this mock object
I do have a delete() method in my repository and I know it's working when I test my route in the browser.
public function delete($id)
{
Note::find($id)->delete();
}
You are stubbing the note repository to return a string, PHP is then trying to retrieve the id attribute of a string, hence the error.
You should stub the repository to return a Note object, something like:
$this->mock->shouldReceive('find')->once()->andReturn(new Note());
Building upon Dave's answer, I was able to figure out what my problem is. I wasn't mocking the delete() method. I didn't understand the need to mock each individual method in my controller that would be called.
I just added this line:
$mock->shouldReceive('delete')->once()->andReturnNull();
Since my delete method is just deleting the note after it is found, I went ahead and mocked it but set it to return null.

How to mock a CakePHP behavior for unit testing

I've just started with unit testing in CakePHP (yay!) and ran into the following challenge. Hope somebody can help me :-)
Situation
My model uses a Behavior to send changes to an API after saving it locally. I would like to fake all calls made to the API during the test (those will be tested seperately) to save load on the API server, and more important, not actually save the changes :-)
I'm using CakePHP 2.4.1.
What I've tried
Read the docs. The manual shows how to do this for Components and Helpers but not for Behaviors.
Google. What I've found:
A Google Group post which says it simply "isn't possible". I don't take no for an answer.
An article explaining how to mock an object. Comes pretty close.
The code from the article reads:
$provider = $this->getMock('OurProvider', array('getInfo'));
$provider->expects($this->any())
->method('getInfo')
->will($this->returnValue('200'));
It might be the wrong direction, but I think that might be a good start.
What I want
Effectively: A snippet of code to demo how to mock a behavior in a CakePHP Model for unit testing purposes.
Maybe this question will result in an addition of the CakePHP manual too as an added bonus, since I feel it's missing in there.
Thanks in advance for the effort!
Update (2013-11-07)
I've found this related question, which should answer this question (partly). No need to mock up the API, instead I can create a Behavior test that the model will use.
I'm trying to figure out what that BehaviorTest should look like.
Use the class registry
As with many classes, behaviors are added to the class registry using the class name as the key, and for subsequent requests for the same object loaded from the classregistry. Therefore, the way to mock a behavior is simply to put it in the class registry before using it.
Full Example:
<?php
App::uses('AppModel', 'Model');
class Example extends AppModel {
}
class TestBehavior extends ModelBehavior {
public function foo() {
throw new \Exception('Real method called');
}
}
class BehaviorExampleTest extends CakeTestCase {
/**
* testNormalBehavior
*
* #expectedException Exception
* #expectedExceptionMessage Real method called
* #return void
*/
public function testNormalBehavior() {
$model = ClassRegistry::init('Example');
$model->Behaviors->attach('Test');
$this->assertInstanceOf('TestBehavior', $model->Behaviors->Test);
$this->assertSame('TestBehavior', get_class($model->Behaviors->Test));
$this->assertSame(['foo' => ['Test', 'foo']], $model->Behaviors->methods());
$model->foo();
}
public function testMockedBehavior() {
$mockedBehavior = $this->getMock('TestBehavior', ['foo', 'bar']);
ClassRegistry::addObject('TestBehavior', $mockedBehavior);
$model = ClassRegistry::init('Example');
$model->Behaviors->attach('Test');
$this->assertInstanceOf('TestBehavior', $model->Behaviors->Test);
$this->assertNotSame('TestBehavior', get_class($model->Behaviors->Test));
$expected = [
'foo' => ['Test', 'foo'],
'bar' => ['Test', 'bar'],
'expects' => ['Test', 'expects'], // noise, due to being a mock
'staticExpects' => ['Test', 'staticExpects'], // noise, due to being a mock
];
$this->assertSame($expected, $model->Behaviors->methods());
$model->foo(); // no exception thrown
$mockedBehavior
->expects($this->once())
->method('bar')
->will($this->returnValue('something special'));
$return = $model->bar();
$this->assertSame('something special', $return);
}
}

CakePHP fatal error: Class 'ErrorHandler' not found

I've generated testsuits via "cake bake testsuit" and used localhost/test.php for my app.
So, the is an error when I tried to run one of test (else tests are valid):
Fatal error: Class 'ErrorHandler' not found in Z:\home\prodvigator\www\cake\libs\object.php on line 201
This models and controllers are generated by scaffold and I don't think that an error is in this sources.
Using:
CakePHP 1.3
The latest SimpleTest
In my case, deleting all the files in the folder /app/tmp/cache/persistent solved the problem.
try checking the generated tests for an error that gets written at the top of the file.
sometimes i've been known to find something like this in both model and controller tests.
Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'America/New_York' for 'EDT/-4.0/DST' instead in /projectname/cake/console/templates/default/classes/test.ctp on line 22
In my case, the error was:
Fatal error: Uncaught Error: Class 'ErrorHandler' not found in C:\[path]\core\cake\libs\object.php on line 211
( ! ) Error: Class 'ErrorHandler' not found in C:\[path]\core\cake\libs\object.php on line 211
The error was happening to me when trying to visit http://localhost/user_accounts/index
I already had the view created at app\views\user_accounts\index.ctp with the following content:
<div>
Text from div
</div>
I had created the corresponding controller as well at app\controllers\user_accounts_controller.php:
<?php
class UserAccountsController extends AppController {
public function index() {
// Render the view in /views/user_accounts/index.ctp
$this->render();
}
}
?>
Since I was not associating a model to this controller, I was missing this: var $uses = array();. It would have saved me time if the error had been more explicit, something such as "You do not have a model associated to this controller".
The fix was:
<?php
class UserAccountsController extends AppController {
// Use this controller without a need for a corresponding Model file.
var $uses = array();
public function index() {
// Render the view in /views/user_accounts/index.ctp
$this->render();
}
}
?>

Grails Controller Testing - Problems with dynamic methods

I'm running some old (but valid, i'm told) tests on a legacy application, and notice that many of them arent working. The error message usually is 'No method signature for some dymamic method'
After using mockDomain I managed to solve that problem.
However, I can't figure out how to test controllers that create objects inside.
For example, I created a sample controller (omitted import statements)
package com.tmp
class DummyController2 {
def index = { }
def createObject={
def emp= new Emp(name:'name',description:'description')
if (emp.validate()){
render 'OK'
}
else{
render 'FAIL'
}
}
}
And then the sample controllerTest
package com.tmp
class DummyController2Tests extends ControllerUnitTestCase{
DummyController2 controller
public void setUp(){
super.setUp()
controller = new DummyController2()
}
public DummyController2Tests(){
super(DummyController2Tests)
}
public void tearDown(){
super.tearDown()
}
void testCreateObject(){
assertEquals 'OK',controller.createObject()
}
}
Now when I run this test, I get the
groovy.lang.MissingMethodException: No
signature of method: Emp.validate() is
applicable for argument types: ()
values: []
Is there a workaround on this? Adding mockDomain statements inside the controller seems very intrusive and wrong. Maybe its just that I'm using an old grails (1.2.1)?
Thanks in advance
Your domain class is not mocked. Add to setUp():
mockDomain Emp