Laravel controller tests, handling redirect - php

I have a bit of an issue, the below code is from one of the methods within my controller that I'm testing.
The scenario is, you save a record and you're automatically directed to 'viewing' that record. So I am passing in the items id upon save to the redirect...
However, when running the tests I receive 'ErrorException: Trying to get property of non-object' if I pass in the id of the object straight off. So the work around I'm doing the pass the test is a ternary condition to see if the output is an object...surely there must be a better way of doing this?
I'm using Mockery, and have created a mock class/interface for the Projects model which is injected into the Projects main controller.
Here's the method:
public function store()
{
// Required to use Laravels 'Input' class to catch the form data
// This is because the mock tests don't pick up ordinary $_POST
$project = $this->project->create(Input::only('projects'));
if (count(Input::only('contributers')['contributers']) > 0) {
$output = Contributer::insert(Input::only('contributers')['contributers']);
}
// Checking whether the output is an object, as tests fail as the object isn't instatiated
// through the mock within the tests
return Redirect::route('projects.show', (is_object($project)?$project->id:null))
->with('fash', 'New project has been created');
}
And heres the test which is testing the redirected route.
Input::replace($input = ['title' => 'Foo Title']);
$this->mock->shouldReceive('create')->once();
$this->call('POST', 'projects');
$this->assertRedirectedToRoute('projects.show');
$this->assertSessionHas('flash');

You have to define the response of your mock when the method create is called to properly simulate the real behavior :
$mockProject = new StdClass; // or a new mock object
$mockProject->id = 1;
$this->mock->shouldReceive('create')->once()->andReturn($mockProject);

Related

Getting 'Method was expected to be called 1 times, actually called 0 times.' while testing in Symfony

I need a small help with a simple test. I'm trying to test blog post delete endpoint where it deletes both the post and its comments. In the postController method, it calls two other methods from two other repositories (postRepository and commentsRepository) to delete the post completely. Here's how the method in the controller looks like:
public function deletePost(Request $request, $postId)
{
$this->postRepository()->deletePost($postId);
$this->commentsRepository()->deleteComments($postId);
return $this->response(new JsonResponse('', 204));
}
I only need to check if deletePost method and deleteComments are being called once. Right now I'm getting this:
Expectation failed for method name is "deletePost" when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Same as deleteComments. Now I feel like the mock is not working properly.
Here's postRepository's deletePost method and commentRepository's deleteComments method:
// postRepository's
public function deletePost($postId)
{
// ... some code
}
// commentsRepository
public function deleteComments($postId)
{
// ... some code
}
Here's the test:
public function testDelete()
{
$post = $this->postRepository()
->findOne(['id' => PostDataSeeder::POST_ID]);
$route = $this->getUrl('api_delete_post', [
'_format' => 'json',
'postId' => $post->getId()
]);
$this->client->request('DELETE', $route, [], [], [
'CONTENT_TYPE' => 'application/json',
]);
// check if deletePost method in postRepository is being called
$postRepositoryMock = $this->createMock(PostRepository::class);
$postRepositoryMock
->expects($this->once())
->method('deletePost')
->with($post->getId());
// check if deleteComments method in commentsRepository is being called
$commentsRepositoryMock = $this->createMock(CommentsRepository::class);
$commentsRepositoryMock
->expects($this->once())
->method('deleteComments')
->with($post->getId());
}
This is the minimal representation of my actual code. Everything works perfectly, except the test. Please help me how to test that.
When you make the call via $this->client->request... you invoke the unchanged controller and therefore unchanged (not mocked) repositories.
You should substitute repositories in your container before issuing the request and 'running' the it on mocked classes. This can be achieved by Symfonys' env-specific services file i.e. services_test.yml or test/services.yml
The bigger problem I see here is that you're trying to run webTests using mocks, which is a bit of a mix of two approaches: web test and unit test.
Web tests should run on unmodified code (as much as it can be done) but on fixed environment. In this case that would mean a separate test database filled with predefined data (fixtures can do that). After the request is made you can get the repo from container and check if the entry was actually deleted.
Unit test is closer to what you are doing. You first create mocks of all dependencies of postController and instantiate it in your test. Then run $postControllerInstance->deletePost(...) with all arguments mocked (or just instantiated objects) and then check which mocked function has been called.
Summarizing in not entirely correct sentence: web tests should check if everything works correctly; Unit tests should check if everything is being called correctly.

Is there a way to act as a user inside of Laravel without hitting the database?

I'm writing unit tests for an API using PHPUnit and Laravel. Most functions I'm testing require that the user is authenticated before the function can be ran. The user data is stored in one table, and their permissions are stored inside of another table. I can fake the user object inside of Laravel, but I need to be able to also pull the corresponding permissions from the other table without having to hit the database like the dingo router currently is doing.
Currently running Laravel 5.8 and PHPUnit 8.1.5. I currently have the users object that I generated from a Laravel factory saved to a text file. I am able to pass that to a function called "actingAsApi" (found on Github, code below) and that allows me to authenticate as that user. However, the function is still going out and getting all permissions for that user from the database. I'm trying to mock or fake the permissions object it is pulling somewhere so that it doesn't need to hit the database at all. I also tried using the built in Passport functions for Passport::actingAs, and those did not work either as they were still hitting the DB (and not really working anyways).
actingAsApi (inside of TestCase.php)
protected function actingAsApi($user)
{
// mock service middleware
$auth = Mockery::mock('Dingo\Api\Http\Middleware\Auth[handle]',
[
Mockery::mock('Dingo\Api\Routing\Router'),
Mockery::mock('Dingo\Api\Auth\Auth'),
]);
$auth->shouldReceive('handle')
->andReturnUsing(function ($request, \Closure $next) {
return $next($request);
});
$this->app->instance('Dingo\Api\Http\Middleware\Auth', $auth);
$auth = Mockery::mock('Dingo\Api\Auth\Auth[user]',
[
app('Dingo\Api\Routing\Router'),
app('Illuminate\Container\Container'),
[],
]);
$auth->shouldReceive('user')
->andReturnUsing(function () use ($user) {
return $user;
});
$this->app->instance('Dingo\Api\Auth\Auth', $auth);
return $this;
}
Test inside of my Test file
public function testActAs() {
$user = 'tests/users/user1.txt';
$this->actingAsApi($user);
$request = new Request;
$t = new TestController($request);
$test = $t->index($request);
}
I expect the actingAsApi function to allow me to also pass in the mock permissions data that corresponds to my mock user object data from the file, but instead it is hitting the database to pull from the permissions table.
EDIT:
So i've been playing around with doing mock objects, and i figured out how to mock the original controller here:
$controlMock = Mockery::mock('App\Http\Controllers\Controller', [$request])->makePartial();
$controlMock->shouldReceive('userHasPermission')
->with('API_ACCESS')
->andReturn(true);
$this->app->instance('App\Http\Controllers\Controller', $controlMock);
but now I can't figure out how to get my call from the other controllers to hit the mocked controller and not a real one. Here is my code for hitting an example controller:
$info = $this->app->make('App\API\Controllers\InfoController');
print_r($info->getInfo('12345'));
How can i make the second block of code hit the mocked controller and not standup a real one like it does in its constructor method?
Finally came on an answer, and it is now fixed. Here's how I did it for those wondering:
$request = new Request;
$controlMock = m::mock('App\API\Controllers\InfoController', [$request])->makePartial();
$controlMock->shouldReceive('userHasPermission')
->with('API_ACCESS')
->andReturn(true);
print_r($controlMock->getInfo('12345'));
Basically, I was trying to Mock the original API controller, and then catch all of the calls thrown at it. Instead, I should've been mocking the controller I'm testing, in this case the InfoController. I can then catch the call 'userHasPermission', which should reach out to the Controller, but I am automatically returning true. This eliminates the need for hitting the database to receive permissions and other info. More information on how I solved it using Mockery can be found here: http://docs.mockery.io/en/latest/cookbook/big_parent_class.html. As you can see, this is referred to as a 'Big Parent Class'. Good luck!

Laravel PHPUnit mock Request

I'm doing a PHPUnit on my controller and I can't seem to mock the Request right.
Here's the Controller:
use Illuminate\Http\Request;
public function insert(Request $request)
{
// ... some codes here
if ($request->has('username')) {
$userEmail = $request->get('username');
} else if ($request->has('email')) {
$userEmail = $request->get('email');
}
// ... some codes here
}
Then on the unit test,
public function testIndex()
{
// ... some codes here
$requestParams = [
'username' => 'test',
'email' => 'test#test.com'
];
$request = $this->getMockBuilder('Illuminate\Http\Request')
->disableOriginalConstructor()
->setMethods(['getMethod', 'retrieveItem', 'getRealMethod', 'all', 'getInputSource', 'get', 'has'])
->getMock();
$request->expects($this->any())
->method('get')
->willReturn($requestParams);
$request->expects($this->any())
->method('has')
->willReturn($requestParams);
$request->expects($this->any())
->method('all')
->willReturn($requestParams);
// ... some codes here
}
The problem here is that when ever I var_dump($request->has('username'); it always return the $requestParams value in which is the whole array. I'm expecting that it should return true as the username key exists in the array.
Then when I delete the username key on the $requestParams, it should return false as it does not contain the username key on the array
Its not ideal to mock Requests, but sometimes you just want to do it anyway:
protected function createRequest(
$method,
$content,
$uri = '/test',
$server = ['CONTENT_TYPE' => 'application/json'],
$parameters = [],
$cookies = [],
$files = []
) {
$request = new \Illuminate\Http\Request;
return $request->createFromBase(
\Symfony\Component\HttpFoundation\Request::create(
$uri,
$method,
$parameters,
$cookies,
$files,
$server,
$content
)
);
}
As far as I can see and understand you're telling your unit test that when you call $request->has() on your request object that it should return the $requestParams array, not true or false, or anything else.
Unless you specifically check what is send with a method call your mock doesn't actually care what is send, it just cares that it was called.
You might want to explore creating an empty request and filling it with data if that is possible in your use case as that'll let you run your unit test with more ease and less issues. This won't work in all cases.
You could include what assertions you're making in your unit test so we can see more clearly what you're running into, but as it is. It returns exactly what you're telling it to return. Even if that's not what you actually want it to return.
Mocks are used to separate your Unit-Test from the rest of your system. As such you usually tend to only check if a specific method is called to see if your code actually exits to the class you mocked and if it has the expected data you'd send along. In some extreme cases you can want to mock the system you're actually testing, but this usually indicates that your code is too dependent on other classes or it's doing too much.
Another reason to use mocks is to satisfy Type Casting constraints in your method calls. In these cases you'll usually create an empty mocked object and fill it with some dummy data your code will accept or break on to test the code.
In your case it seems you want to check if your code actually works correctly and for this I'd suggest either not mocking the request, or making specific tests where you tell it to return true, or false (test for both cases)
So something along the lines of:
$request->expects($this->any())
->method('has')
->with('username')
->willReturn(true); // or false in your next test
Edit:
As you mentioned in the comment Below you ran into the issue that you're using the has method multiple times in your code and ran into issues.
The Questions I've linked to in my response comment go into greater detail but to sum it up, you can use an inline function or the at() method to deal with multiple cases.
With at() you can supply specific iterations of the code to hit only that bit of the test. It has been mentioned that this makes your tests rather brittle as any has added before the previous ones would break the test.
$request->expects($this->at(0))
->method('has')
->with('username')
->willReturn('returnValue');
$request->expects($this->at(1))
->method('has')
->with('email')
->willReturn('otherReturnValue');
The inline function (callback) solution would allow you to customize your test to allow multiple cases and to return data as required. Unfortunately I'm not too familiar with this concept as I haven't used it myself before. I suggest reading the PHPUnit docs for more information about this.
In the end I'd still suggest not mocking the request and instead making an empty request that you'll fill with the data you want to check. Laravel comes with some impressive methods that'll let you manually fill the request with a lot of data you'd usually test against.
For example you can add data (post/get data) by using
request->add(['fieldname' => 'value'])
As a last few pointers I'd like to mention that it seems you use var_dump.
Laravel comes with two of it's own functions that are similar and quite useful in debugging.
You can use dd(); or dump();
dd(); dumps and stops the execution of code, while dump(); just outputs whatever you decide. so you could do dd($request); or dump($request); and see what the variables/class objects/etc holds. It'll even put it in a rather spiffy layout with some Javascript and such to allow you to see what's in it and such. Might want to check it out if you didn't knew it existed.
If you use request()->user() you can set user resolver. It allows you to return user you want. I had the same problem and solution for me was like this:
public function testSomething()
{
$user = User::factory()->create();
request()->setUserResolver(function() use ($user) {
return $user;
});
// Dumped result will be newly created $user
dd(request()->user());
}
A simpler answer than #Ian, if your situation is simpler:
Per https://stackoverflow.com/a/61903688/135114,
if
your function under test takes a $request argument, and
you don't need to do funky stuff to the Request—real route paths are good enough for you
... then you don't need to "mock" a Request (as in, mockery),
you can just create a Request and pass it, e.g.
public function test_myFunc_condition_expectedResult() {
...
$mockRequest = Request::create('/path/that/I_want', 'GET');
$this->assertTrue($myClass->myFuncThat($mockRequest));
}
I was running unit test on a FormRequest child class with Laravel Framework 9.3.0 and get this error:
Error : Call to a member function get() on null
/vendor/symfony/http-foundation/Request.php:676
# code failing
$customRequest->get('parameter');
As you can see in Request class, there are lot of public properties (source code):
public $attributes;
public $request;
public $query;
public $server;
public $files;
public $cookies;
public $headers;
...
This is the way i find to partially mock Request class, example below:
# test code
$this->customRequest = new CustomRequest();
$parameterBag = \Mockery::mock(ParameterBag::class);
$parameterBag->shouldReceive('get')
->with('parameter', \Mockery::any())
->andReturn(null) // anything
;
$this->customRequest->attributes = $parameterBag;

How to reload/refresh model from database in Laravel?

In some of my tests, I have a user model I have created and I run some methods that need to save certain attributes. In rails, I would typically call something like user.reload which would repopulate the attributes from the database.
Is there a way in laravel to do that? I read through the api and couldn't find a method for it: http://laravel.com/api/4.1/Illuminate/Database/Eloquent/Model.html Any ideas on the "right" way to do this?
There was a commit submitted to the 4.0 branch made in August to add a reload() method, but so far it hasn't been merged with the newer Laravel branches.
But... Laravel 5 is providing a "fresh()" method that will return a new instance of the current model. Once you're using Laravel 5.0 or newer, you can reload a model like this:
$model = $model->fresh();
Note that fresh() doesn't directly update your existing $model, it just returns a new instance, so that's why we need to use "$model = ". It also accepts a parameter which is an array of relations that you want it to eager load.
If you aren't yet using Laravel 5 but you want the same functionality, you can add this method to your model(s):
public function fresh(array $with = array())
{
$key = $this->getKeyName();
return $this->exists ? static::with($with)->where($key, $this->getKey())->first() : null;
}
Update: If you're using Laravel 5.4.24 or newer, there is now a $model->refresh() method that you can use to refresh the object's attributes and relationships in place rather than fetching a new object like fresh() does. See Jeff Puckett answer for more specifics on that.
Thanks to PR#19174 available since 5.4.24 is the refresh method.
$model->refresh();
This way you don't have to deal with reassignment as is shown in other answers with the fresh method, which is generally not helpful if you want to refresh a model that's been passed into another method because the variable assignment will be out of scope for the calling contexts to use later.
refresh() is a mutable operation: It will reload the current model instance from the database.
fresh() is an immutable operation: It returns a new model instance from the database. It doesn't affect the current instance.
// Database state:
$user=User::create([
'name' => 'John',
]);
// Model (memory) state:
$user->name = 'Sarah';
$user2 = $user->fresh();
// $user->name => 'Sarah';
// $user2->name => 'John'
$user->refresh();
// $user->name => 'John'
I can't see it either. Looks like you'll have to:
$model = $model->find($model->id);
You can also create one yourself:
public function reload()
{
$instance = new static;
$instance = $instance->newQuery()->find($this->{$this->primaryKey});
$this->attributes = $instance->attributes;
$this->original = $instance->original;
}
Just tested it here and it looks it works, not sure how far this goes, though, Eloquen is a pretty big class.
I believe #Antonio' answer is the most correct, but depending on the use case, you could also use a combination of $model->setRawAttributes and $model->getAttributes.
$users = User::all();
foreach($users as $user)
{
$rawAttributes = $user->getAttributes();
// manipulate user as required
// ..
// Once done, return attribute state
$user->setRawAttributes($rawAttributes);
}
The primary downside to this is that you're only "reloading" the data attributes, not any relationships you've altered, etc. That might also be considered the plus side.
EDIT
As of L5 - fresh() is the way to go

Non-public accessible function in CakePHP

I have built a simple Notification system in my Cake app that I want to have a function that will create a new notification when I call a certain method. Because this is not something the user would actually access directly and is only database logic I have put it in the Notification model like so:
class Notification extends AppModel
{
public $name = 'Notification';
public function createNotification($userId, $content, $url)
{
$this->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
}
And then whenever I want to create a notification within my app I just do:
$this->Notification->createNotification($userId,'Test','Test');
However this doesn't work! The controller is talking to the model fine, but it doesn't create the row in the database... I'm not sure why... but it would seem I'm doing this wrong by just doing all the code in the model and then calling it across the app.
Edit: Based on answers and comments below, I have tried the following the code to create a protected method in my notifications controller:
protected function _createNotification($userId, $content, $url)
{
$this->Notification->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
Now the thing that is stumping me still (apologies if this is quite simple to others, but I have not used protected methods in CakePHP before) is how do I then call this from another controller? So for example If have a method in my PostsController and want to create a notification on successful save, how would I do this?
I thought about in my PostsController add method:
if($this->save($this->request-data){
$this->Notification->_createNotification($userId,'Test','Test');
}
But being protected I wouldn't be able to access the method from outside of the NotificationsController. Also I'm using the same syntax as if I was calling a function from a model so again it doesn't feel right.
Hopefully someone can help me out and get me back on track as this is a new area to me.
the controller should pass all data to the model
$this->createNotification($this->request->data);
the model then can use the data:
public function createNotification(array $data) {
$key = $data[$this->alias]['key'];
$data[...] = ...;
$this->create();
return $this->save($data);
}
you never ever try to access the controller (and/or its request object) from within a model.
you can also invoke the method from other models, of course:
public function otherModelsMethod() {
$this->Notification = ClassRegistry::init('Notification');
$data = array(
'Notification' => array(...)
);
$this->Notification->createNotification($data);
}
and you can make your methods verbose, but that usually makes it harder to read/understand/maintain with more and more arguments:
public function createNotification($userId, $content, $url) {
$data = array();
// assign the vars to $data
$data['user_id'] = $userId;
...
$this->create();
return $this->save($data);
}
so this is often not the cake way..
Methods in a model are not "publicly accessible" by definition. A user cannot call or invoke a method in a model. A user can only cause a controller action to be initiated, never anything in the model. If you don't call your model method from any controller, it's never going to be invoked. So forget about the "non-public" part of the question.
Your problem is that you're working in the model as if you were in a controller. There is no request object in a model. You just pass a data array into the model method and save that array. No need for $this->request. Just make a regular array(), put the data that was passed by the controller in there and save it.
The whole approach is totally wrong in the MVC context IMO and screams for the use of the CakePHP event system. Because what you want is in fact trigger some kind of event. Read http://book.cakephp.org/2.0/en/core-libraries/events.html
Trigger an Event and attach a global event listener that will listen for this kind of events and execute whatever it should do (save something to db) when an event happens. It's clean, flexible and extendible.
If you did a proper MVC stack for your app most, if not all, events aka notifications should be fired from within a model like when a post was saved successfully for example.
This is what I have ended up doing. While it certainly isn't glamorous. It works for what I want it to do and is a nice quick win as the notifications are only used in a few methods so I'm not creating a large amount of code that needs improving in the future.
First to create a notification I do the following:
$notificationContent = '<strong>'.$user['User']['username'].'</strong> has requested to be friends with you.';
$notificationUrl = Router::url(array('controller'=>'friends','action'=>'requests'));
$this->Notification->createNotification($friendId,$notificationContent,$notificationUrl);
Here I pass the content I want and the URL where the user can do something, in this case see the friend request they have been notified about. The url can be null if it's an information only notification.
The createNotification function is in the model only and looks like:
public function createNotification($userId, $content, $url = null)
{
$this->saveField('user_id',$userId);
$this->saveField('content',$content);
$this->saveField('url',$url);
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
This creates a new record in the table with the passed content, sets its status to 0 (which means unread) and the date it was created. The notification is then set as read when a user visits the notifications page.
Again this is most probably not an ideal solution to the problem outlined in this question... but it works and is easy to work with And may prove useful to others who are learning CakePHP who want to run functions from models when building prototype apps.
Remember nothing to stop you improving things in the future!
First of all, you can improve your last solution to do one save() (instead of 5) the following way:
public function createNotification($userId, $content, $url = null){
$data = array(
'user_id' => $userId,
'content' => $content,
'url' => $url,
'datetime' => date('Y-m-d H:i:s'),
'status' => 0
);
$this->create();
$this->save($data);
}
When I began programming CakePHP(1.3) more than a year ago I also had this problem.
(I wanted to use a function of a controller in any other controller.)
Because I didn't know/researched where to place code like this I've done it wrong for over a year in a very big project. Because the project is really really big I decided to leave it that way. This is what i do:
I add a function (without a view, underscored) to the app_controller.php:
class AppController extends Controller {
//........begin of controller..... skipped here
function _doSomething(){
//don't forget to load the used model
$this->loadModel('Notification');
//do ur magic (save or delete or find ;) )
$tadaaa = $this->Notification->find('first');
//return something
return $tadaaa;
}
}
This way you can access the function from your Notification controller and your Posts controller with:
$this->_doSomething();
I use this kind of functions to do things that have nothing to do with data submittance or reading, so i decided to keep them in the app_controller. In my project these functions are used to submit e-mails to users for example.. or post user actions to facebook from different controllers.
Hope I could make someone happy with this ;) but if you're planning to make a lot of these functions, it would be much better to place them in the model!

Categories