I've decided to make some unit tests for my app and I have a problem.
I have logic part
private $model;
__construct($model)
{
$this->model = $model;
}
public function getData()
{
$data = $this->model->getData();
return $data;
}
model
public function $getData()
{
return self::where('id', '=', 1)->first();
}
and now I would like to make unit test.
I know how to mock right model
$collection = new Collection();
$collection->push(
new Model(
)
);
but have no idea how to put data into it. I've tried put data as attribute but it throws me MassAssigmentException
Would be grateful if someone know and share his knowledge.
Related
I have setup my model as following:
class Items extends Model {
use HasFactory;
protected $table = 'item';
protected $primaryKey = 'id';
protected $connection = 'mysql';
public $timestamps = false;
protected $fillable = ['user_id', 'title', 'desc', 'start_datetime', 'due_datetime', 'priority', 'status'];
public function getManager() {
return $this->belongsTo(User::class, 'user_id');
}
public function getAssignees() {
return $this->belongsToMany(User::class);
}
}
I am getting all items using the controller method below, what I want to do is to populate the user_id field in each of the items using getManager() method I declared in my Item model. I know how to do this when getting only one item, but how to populate every record when getting all of them?
public function getall() {
try {
$items = Item::get();
return response()->json(['items' => $items], 200);
} catch (Throwable $err) {
return response()->json($err, 400);
}
}
I have tried this but no luck:
public function getall() {
try {
$items = Item::get();
$items = array_map(function ($el) {
return $el->manager = $el->getManager()->get();
}, $items);
return response()->json(['items' => $items], 200);
} catch (Throwable $err) {
return response()->json($err, 400);
}
}
There are a few things here that I have some concerns about. Your code may work, but you are also doing more than you need to and not using Laravel how it was meant to be used.
Model Name
Your model name is Items, but it should be singular, Item. This helps Laravel automate things so you have less work to do.
https://laravel.com/docs/8.x/eloquent#eloquent-model-conventions
class Item extends Model {
Database Settings
You've set the $table, $primaryKey, and $connection attributes, but these should be automatic. You can probably remove them.
protected $table = 'items'; // assuming your model name is Item, this would automatically be 'items'
protected $primaryKey = 'id'; // default is already 'id'
protected $connection = 'mysql'; // default is your main db, probably already 'mysql', unless if you have multiple db connections
Timestamps
I'm not sure why you'd want to turn timestamps off. You definitely can but I always find it helpful to know when something was created or last updated. Since Laravel handles the timestamps for you, I'd suggest leaving it on, but it's up to you.
https://laravel.com/docs/8.x/eloquent#timestamps
public $timestamps = false;
Manager Relationship
Your manager relationship is getManager but should just be manager. It will still work, but isn't how Laravel was meant to work. I would suggest changing it to manager(), and not specifying the column name. This would make the column name automatically manager_id, so you'd have to update that. Or you can keep the column name 'user_id'.
https://laravel.com/docs/8.x/eloquent-relationships#one-to-many-inverse
public function manager() {
return $this->belongsTo(User::class);
}
Assignees Relationship
Same as with the Manager relationship, you should change getAssignees() to assignees(). I'm assuming you already have a database migration set up for your 'item_user' table that Laravel will look for. If not, check the Laravel docs on how to set it up.
https://laravel.com/docs/8.x/eloquent-relationships#many-to-many
public function assignees() {
return $this->belongsToMany(User::class);
}
Retrieving Items
Finally, with the above changes, getting all Items should be easy. To load the relationships, use the $with method. This is called Eager Loading. Check the docs for more info.
https://laravel.com/docs/8.x/eloquent-relationships#eager-loading
$items = Item::with('manager','assignees')->get();
Returning Response Codes
You were returning your responses incorrectly. You do not need to set the response code 200, as this is the default. If you are going to set it to something else, put the code in the response() method, instead of the json() method.
https://laravel.com/docs/8.x/responses
return response()->json(['items' => $items]);
return response($err,400);
Now putting it all together, your Item model should look something like this:
class Item extends Model {
use HasFactory;
protected $fillable = ['manager_id', 'title', 'desc', 'start_datetime', 'due_datetime', 'priority', 'status'];
public function manager() {
return $this->belongsTo(User::class);
}
public function assignees() {
return $this->belongsToMany(User::class);
}
}
public function getall() {
try {
$items = Item::get()
->transform(function($el){
$el->manager = $el->getManager()->get();
);
return response()->json(['items' => $items], 200);
} catch (Throwable $err) {
return response()->json($err, 400);
}
}
Try the transform method on your results and it would work.
https://laravel.com/docs/8.x/collections#method-transform
the transform function would basically just iterate over the results and do whatever it is told to like a for loop but for collections.
Also, to make your query efficient avoid the use of loading the relation in the transform function and and use with function of laravel to make it efficient
I am trying to mock a model where I am doing a query on the db. At first I was mocking the where function on the below but then I realised it's actually first that provides the result, however this still doesn't work. I am aware I could just use the database, but our docker setup is super slow and I can't use SQLite as a previous developer created a migration at some point that removes a foreign key.
Test:
protected function setUp(): void
{
parent::setUp();
$this->calendarEventBookingRepository = app(CalendarEventBookingRepository::class);
$this->calendarEventBooking = Mockery::mock(CalendarEventBooking::class);
}
/** #test */
function bookSingleCustomerReturnsNull()
{
$calendarEvent = factory(CalendarEvent::class)->create();
$calendarEventBooking = factory(CalendarEventBooking::class);
$data = new \stdClass();
$data->customer_id = 1;
$this->calendarEventBooking->shouldReceive('first')->once()->andReturn($calendarEventBooking);
$this->app->instance(CalendarEventBooking::class, $this->calendarEventBooking);
$result = $this->calendarEventBookingRepository->bookSingleCustomer($calendarEvent, $data);
$this->assertEquals(null, $result);
}
Function being tested:
public function bookSingleCustomer(CalendarEvent $event, $data)
{
$this->event = $event;
DB::transaction(function () use ($data) {
$alreadyBooked = $this->modelClassName::where([
['customer_id', $data->customer_id]
])->first();
if ($alreadyBooked) {
return null;
}
return "hello";
});
}
Test Output:
Mockery\Exception\InvalidCountException: Method first(<Any Arguments>) from Mockery_0_Models_CalendarEventBooking should be called
exactly 1 times but called 0 times.
I think you should mock the where method as well.
$this->calendarEventBooking->method('where')->willReturnSelf();
I am using Laravel 5.2 and I need to get specific values from the database with a leftjoin. The code I am using is as follow:
public function commentList(Request $request)
{
$inputs = $request->all();
$commentList = Comment::select(
'projects_comments.id as comment_id',
'u.name as user_name',
'projects_comments.comment as comment',
'projects_comments.created_at as created_at'
);
$commentList->leftjoin('users AS u', 'projects_comments.user_id', '=', 'u.id');
if (!empty($inputs['project_ids'])) {
$commentList->where(function ($query) use ($inputs) {
foreach ($inputs['project_ids'] as $i) {
$query->orWhere('projects_comments.project_id', $i);
}
});
};
$data = $commentList->get();
return $data;
}
It works fine but I would like to know if there is a better way to do this using eloquent but I can't really understand how to write this for eloquent to work. I need to get all the comments from an array of project ids.
I have the following model for Comment:
class Comment extends Model
{
protected $table = 'projects_comments';
public $timestamps = true;
protected $guarded = ['id'];
public function project()
{
return $this->belongsTo('App\Project', 'project_id');
}
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
}
I assume what you want is to get Comments (with their users) that belongs to specific Projects provided by the user as an array of IDS
Comment::whereIn('project_id', $inputs['project_ids'])->with('user')->get();
And if you only want the id and name of the user associated with the comment, pass the fields to the with function like so
Comment::whereIn('project_id', $inputs['project_ids'])
->with('user:id,name')->get();
I am trying to develop a web application using laravel 5.3 and came up with a problem couldn't solve so far.
Heres the Context.
I got a simple Laravel Model called Section which implements a constructor as shown below;
public function __construct($title = null, array $attributes = array()){
parent::__construct($attributes);
try {
\App\logic_model\system\internal\Logger::debug_dump("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
\App\logic_model\system\internal\Logger::debug_dump("create section ".$title);
$this->title = $title;
$this->save();
return $this;
} catch(\Exception $e){
\App\logic_model\system\internal\Logger::dev_dump($e->getMessage());
throw $e;
}
}
Instance creation using the constructor seems to be working pretty well.
I wrote a function find_by_title as shown below:
public static function find_by_title($title){
$section = \App\logic_model\sections\Section::where("title", "=", $title)->first();
return $section;
}
Here occurs the Problem (the unexpected behaviour): The Eloquent where function seems to call my overloaded constructor instead of the default constructor.
My question is: Why is that? How to fix it?
This is completely expected behavior. As you created custom constructor, each time new model is created (in fact, this happens when you call first(), not where) then this constructor is used to create new object.
If you need custom constructor like this, I would recommend you to create static custom method that will do the same for example like this:
public static function createWithTitle($title = null, array $attributes = array()){
$model = new static($attributes);
try {
\App\logic_model\system\internal\Logger::debug_dump("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
\App\logic_model\system\internal\Logger::debug_dump("create section ".$title);
$model->title = $title;
$model->save();
return $model;
} catch(\Exception $e){
\App\logic_model\system\internal\Logger::dev_dump($e->getMessage());
throw $e;
}
}
I've tried to understand a process of saving a model with multiple relationships but I still can't figure out how to do it "kosher" way.
To begin with - I have an Event model that belongs to a category (Eventcat) and a Location:
// Event.php
class Event extends Eloquent {
protected $table = 'events';
public function location()
{
return $this->belongsTo('Location');
}
public function eventcat()
{
return $this->belongsTo('Eventcat');
}
public function users()
{
return $this->belongsToMany('User');
}
}
// Location.php
class Location extends Eloquent
{
protected $table = 'locations';
public function events()
{
return $this->hasMany('Event');
}
}
// Eventcat.php
class Eventcat extends Eloquent
{
protected $table = 'eventcats';
public function events()
{
return $this->hasMany('Event');
}
}
I've seeded the database with a few categories and locations and now I trying to get events saving work. I thought that the $event->eventcat()->associate( $eventcat ) would work but I got a Call to undefined method eventcat() error.
public function postCreateEvent() {
$event = new Event();
$eventcat = Eventcat::find( Input::get('event-create-eventcat[]') );
$location = Location::find( Input::get('event-create-location[]') );
$event->title = Input::get('event-create-title');
$event->description = Input::get('event-create-description');
$event->price = Input::get('event-create-price');
$event->start_date = Input::get('event-create-start_date');
$event->end_date = Input::get('event-create-end_date');
$event->eventcat()->associate( $eventcat );
$event->location()->associate( $location );
$event->save();
}
I've read the documentation, API and a few threads here but I still can't figure out the best way to deal with this.
Thanks for replies!
I would actually bet that you have a conflict in your class name. Laravel contains an Event class and I wonder if that isn't what's being called in your code. As a quick test, you could rename your class FooEvent and see if it works.
The best solution is probably namespacing your model (see http://chrishayes.ca/blog/code/laravel-4-methods-staying-organized for a quick intro) so that your model can still be called Event without conflicting with the builtin class.