Set mocked Eloquent model public attributes - laravel-5

I have a model Model1 that has many Model2.
An instance of Model1 is passsed as a paramer to the fuction that I want to test. the function that I would like to test looks like this:
function funnctionTotest(Model1 $moel1)
{
foreach($moel1->model2s as $model2) {
....
}
}
The example below does not work. I am putting it here as a starting point for a real solution (array() should be replaced by something else maybe a collections of Model2 instances).
$mock = $this->getMockBuilder('Model1')
->disableOriginalConstructor()
->getMock();
$mock->setAttribute('Model2s', array());
Thanks

$mock->setAttribute('Model2s', array());
Won't do anything because in PHPUnit, by default, all mocked objects' methods are stubbed out, meaning they do nothing and return NULL. You could avoid this by doing one of two things:
Set a list of methods to stub out in the mock builder. Any methods not specified in this list retain their default functionality.
$mock = $this->getMockBuilder('Model1')
->disableOriginalConstructor()
->setMethods(array('doSomeStuff', 'doSomeOtherStuff'))
->getMock();
$mock->setAttribute('Model2s', array());
Don't mock Model1 at all--just instantiate it as a normal instance of Model1.
$model1 = new Model1();
$model1->setAttrbute('Model2s', array());
If there are no methods that need to be stubbed in Model1 in order for your test case to run, option #2 makes the most sense.

Here is the solution that works perfectly for me:
$campaignMock = $this->getMockBuilder('Model2')
->disableOriginalConstructor()
->setMethods(array('setAttribute', 'getAttribute'))
->getMock();
$model2 = new Model2();
$model2->someprop = 'somevalue';
$campaignMock->expects($this->at(0))
->method('getAttribute')
->with('model2s')
->willReturn(array($model2));
Thanks

Related

How to assert that Laravel Controller returns view with proper data?

I need to know how to assert that Laravel Controller returns view with proper data.
My simple controller function:
public function index() {
$users = User::all();
return view('user.index', ['users' => $users]);
}
I am using functions such as assertViewIs to get know if proper view file is loaded:
$response->assertViewIs('user.index');
Also using asserViewHas to know that "users" variable is taken:
$response->assertViewHas('users');
But I do not know how to assert if retrieve collection of users contain given users or not.
Thanks in advance.
In tests I would use the RefreshDatabase trait to get a clean database on each test. This allows you to create the data you need for that test and make assumptions on this data.
The test could then look something like this:
// Do not forget to use the RefreshDatabase trait in your test class.
use RefreshDatabase;
// ...
/** #test */
public function index_view_displays_users()
{
// Given: a list of users
factory(User::class, 5)->create();
// When: I visit the index page
$response = $this->get(route('index'));
// Then: I expect the view to have the correct users variable
$response->assertViewHas('users', User::all());
}
The key is to use the trait. When you now create the 5 dummy users with the factory, these will be the only ones in your database for that test, therefore the Users::all() call in your controller will return only those users.

How to use multiple models in one controller

I have six models that I need to get in one instance in my controller. How can I do that?
I have my six models:
CommentaireCritique
CommentaireNews
CommentaireDossier
CommentaireEpisode
CommentaireSerie
CommentaireTrailer
They all have the same structure in my database, and I would like to show the latest comms on one single page. I don't know if it's possible to bind them in a single controller. I tried that, but it's not working.
public function index()
{
$comms = CommentaireCritique::all() && CommentaireNews::all()
&& CommentaireDossier::all() && CommentaireEpisode::all()
&& CommentaireSerie::all() && CommentaireTrailer::all()
->get();
return view('admin.commentaires.index', compact('comms'));
}
just after the namespace , before the class declaration
use yourAppNameSpace/modelName
There is no limits to the number of models you can instantiate in your controller as long as you declare them above correctly.I think what you need is way to merge the result of all the models if that is so, then you have to use the merge method, otherwise can you please clarify a little bit your question.
yes, you can retrieve them at one controller,
you're already halfway there, you should separate on different variable
public function index()
{
$comms = CommentaireCritique::all()
$news = CommentaireNews::all()
$dossier = CommentaireDossier::all()
$episodes = CommentaireEpisode::all()
$series = CommentaireSerie::all()
$trailers = CommentaireTrailer::all()
return view('admin.commentaires.index', compact('comms','news','dossier','episodes','series','trailers'));
}
if you want put them in one variable, you can use collection docs
All of the results from all() function returns laravel collections. So use concat() function to concatenate all those into one collection
public function index()
{
$coms = CommentaireCritique::all()
->concat(CommentaireNews::all())
->concat(CommentaireDossier::all())
->concat(CommentaireEpisode::all())
->concat(CommentaireSerie::all())
->concat(CommentaireTrailer::all());
return view('admin.commentaires.index', compact('comms'));
}

Refactor Repeated Controller Methods in Laravel

I'm a bit confused about my code in Laravel. I have a member that can subscribe to one or more lessons. I have three tables: members, lessons, and lesson_member.
I created a member form, and from this form, I can register the member in the lesson (I need to get the lesson id, and I put the record in the pivot table).
Then I have the lesson form; I can create a new lesson and put inside one or more members (in this case I need the member code).
Those two functions are nearly the same, but the parameters are different. In my solution, I created two different controllers with two different functions.
LessonController
<?php
public function addMember(Request $request)
{
$lessonMember = new LessonMember();
$lessonId = $request->session()->get('lessonId', 1);
if ((!($lessonMember::where('lesson_id', '=', $lessonId)
->where('license_member_id', '=', $request->memberId)
->exists()))) {
$lessonMember->lesson_id = $lessonId;
$lessonMember->license_member_id = $request->memberId;
$lessonMember->save();
$member = LicenseMember::find($request->memberId)->member;
return response()->json(['user_saved' => $member, 'llm' => $lessonMember, 'actualMembers' => $actualMembers]);
}
}
MemberController
<?php
public function addLesson(Request $request)
{
$lessonMember = new LessonMember();
$memberId = $request->session()->get('memberId', 1);
if ((!($lessonMember::where('lesson_id', $request->lessonId)
->where('license_member_id', $memberId)
->exists()))) {
$lessonMember->lesson_id = $request->lessonId;
$lessonMember->license_member_id = $memberId;
$lessonMember->save();
$member = LicenseMember::find($memberId)->member;
return response()->json(['user_saved' => $member, 'llm' => $lessonMember]);
}
}
I have the same problem with the removeFromLesson() method and in the updateLessonMember method, but the solution should be similar. It is for sure not DRY, and I think I have to put some code in the model (or somewhere else), but I don't know how to proceed. I want to refactor to have a clean solution. I read about traits, but I don't know if it's the right way to follow.
LessonController and MemberController extends a BaseController.
Inside that base controller, create a function called add_lesson_member(you can call it whatever you want)
Then you just need to call this function using $this->add_lesson_member() inside your LessonController or MemberController.

How do I convert an API resource class in Laravel 5.5 to an array BEFORE returning from controller?

Ordinarily, in Laravel 5.5, when using an api resource class, you simply return the resource class instance from your controller method, like so:
public function show(Request $request, MyModel $model)
{
return new MyModelResource($model);
}
This converts the model to an array (and ultimately to json) in the response to the client.
However... I am trying to figure out how to convert everything to an array BEFORE returning it from the controller method. I tried this:
public function show(Request $request, MyModel $model)
{
$array = (new MyModelResource($model))->toArray($request);
// ...
}
The problem here is that any relationships loaded on the resource aren't also converted to an array. They show up inside $array as an instance of a resource class. Obviously calling toArray() manually does not result in a recursive call, and methods such as ->whenLoaded('relationship_name') aren't really respected either.
So how do I get Laravel to do everything it does to convert the resource to an array recursively WITHOUT having to return it from my controller method?
I believe what you are looking for is the resolve method on the resource class. See definition.
From the looks of it, it should handle converting the relationships into an array as well. Just be sure you are setting up your resource relationships properly.
Neither the toArray() or resolve() methods convert the related models to arrays which is really annoying because you'd expect them to.
You're better off using toResponse(null) which will return a JsonRepsonse object. Which you can then use the getContent() method for a json encoded string or the getData() method for an object.
So if you wanted an array not wrapped in a data variable it would be:
$array = json_decode(
json_encode(
(new MyModelResource($model))
->toResponse(null)
->getData()
->data
),
true);
Ugly but it works unlike the accepted answer.

Is there any decent way to Decorate models returned from a Magento `[model]_load_after`event?

I'm trying to overwrite some methods in models, and I'm on a mission to avoid overwrites and rewrites of models for maximum compatibility with other modules.
I figured the best way would be to simply decorate models after they are loaded from Magento, however as far as I can tell because of the way the observer pattern in Magento is written it's impossible to accomplish this. ( As Magento always returns the reference to $this ), and the lack of interfaces might also cause trouble later down the road? See this partial of Mage/Core/Model/Abstract.php
/**
* Processing object after load data
*
* #return Mage_Core_Model_Abstract
*/
protected function _afterLoad()
{
Mage::dispatchEvent('model_load_after', array('object'=>$this));
Mage::dispatchEvent($this->_eventPrefix.'_load_after', $this->_getEventData());
return $this;
}
My question boils down to the title, is there a decent way of accomplishing this?, or am I simply stuck with rewrites :(?
The path I would like to take is;
On event [model]_load_after
return new Decorator($event->getObject())
Where the decorator class in my case would be something like;
public function __construct(Mage_Sales_Model_Order_Invoice $model)
{
parent::__construct($model); // sets $this->model on parent class, see below
}
// overwrite the getIncrementId method
public function getIncrementId()
{
return '12345';
}
// partial of parent class
public function __call($method, array $args)
{
return call_user_func_array(array($this->model, $method), $args);
}
And just some pseudo-code for extra clarification;
$model = Mage::getModel('sales/order_invoice')->load(1);
echo get_class($model);
Namespace_Decorator **INSTEAD OF** Mage_Sales_Model_...
echo $model->getIncrementId();
'12345' **INSTEAD OF** '1000001' ( or whatever the format might be )
Thanks for your time reading / commenting, I really hope there actually is a way to accomplish this in a clean fashion without making use of code overrides or rewrites of models.
Edit: extra clarification
Basically what I would like is to return an instance of the Decorator in a few cases, the sales_invoice being one of them and customer the other. So when any load() call is made on these models, it will always return the instance of the Decorator instead of the Model. Only method calls that the decorator overrides would be returned, and any other method calls would "proxied" through __call to the decorated object.
I'm not sure if I got your question right but here goes.
I think you can use the event [model]_load_after and simply do this:
$object = $event->getObject();
$object->setIncrementId('12345');
Or if you want to use a decorator class make it look like this:
public function __construct(Mage_Sales_Model_Order_Invoice $model)
{
parent::__construct($model);
$model->setIncrementId($this->getIncrementId());
}
public function getIncrementId()
{
return '12345';
}
I know that this is not exactly a decorator pattern but it should work.
I know that when adding a new method to the 'decorator' class you need to add it to attach data to the main model.
This is just my idea. I haven't got an other.
[EDIT]
You can try to rewrite the load method on the object to make it return what you need. But I wouldn't go that way. You can end up screwing a lot of other things.
I don't think there is an other way to do it because load always returns the current object no mater what you do in the events dispatched in the method. see Mage_Core_Model_Abstract::load()
public function load($id, $field=null)
{
$this->_beforeLoad($id, $field);
$this->_getResource()->load($this, $id, $field);
$this->_afterLoad();
$this->setOrigData();
$this->_hasDataChanges = false;
return $this;
}
By making it return new Decorator($this), you might achieve what you need, but just make sure that when calling $model->doSomething() and doSomething() is not a method in your decorator you still end up calling the original method on the model.

Resources