Tests in Laravel not picking up collection attributes - php

I have a test in Laravel 7 constructed as follows:
<?php
namespace Tests\Feature\Http\Controllers\Auth;
use App\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class LoginControllerTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic feature test example.
*
* #return void
*/
public function testLoginPractitioner()
{
$user = factory(User::class, 1)->make();
dump($user);
$response = $this->post('/api/login', [
'phone_number' => $user->phone_number,
'password' => $user->password
], [
'Accept' => 'application/json',
'Content_Type' => 'application/json'
]);
$this->assertDatabaseHas('users', [
'phone_number' => $user->phone_number,
]);
}
}
With the user factory defined as this:
$factory->define(User::class, function (Faker $faker) {
return [
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'phone_number' => '12' . $faker->numerify('#########'),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
'is_admin' => false
];
});
When I dump the user object created in the test, I can see that it has a phone_number attribute:
#attributes: array:6 [
"email" => "leonora.tromp#example.com"
"email_verified_at" => "2021-01-31 11:25:02"
"phone_number" => "12326385883"
"password" => "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi"
"remember_token" => "Oy8DfAonMu"
"is_admin" => false
]
But my test keeps failing and I get this message, as if it has not phone_number attribute:
1) Tests\Feature\Http\Controllers\Auth\LoginControllerTest::testLoginPractitioner
Exception: Property [phone_number] does not exist on this collection instance.
Additionally, the assert fails even if I use a number that I am sure is in the database. Why is this happening?

Your problem is $user is a Collection, when you give a factory an amount of models to create, it will return a Collection instance contained the models created in your case 1. Secondly for the model to be saved to the db you should call create() and not make().
Changing the user creation code to the following should solve the problem.
$user = factory(User::class)->create();
If you at a time need to create multiple users, you need to get them out of the Collection. As you seem confused about Collections, probably reading up on Collections would be wise.

Related

Laravel access relationship models in factories

I'm working inside a Laravel 9 project and am using model factories. I have User which can have one Company.
I need the CompanyFactory details to be linked to the User, such as the first name and last name. The user_id is already mapped with Laravel.
This is my attempt, or what I thought I could do within the CompanyFactory:
$this->user->first_name
Which is undefined?
Here's my seeder:
// development seeders
User::factory(2)
->has(Affiliate::factory()->count(50))
->has(Company::factory()->count(1))
->has(Country::factory()->count(3))
->create();
And my CompanyFactory
<?php
namespace Database\Factories;
use App\Models\User;
use App\Models\Country;
use App\Models\Company;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Carbon\Carbon;
class CompanyFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Company::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
Log::debug('user', [
'user' => $this->user,
]);
return [
'contact_first_name' => $user->first_name,
'contact_last_name' => $user->last_name ? $user->last_name : null,
'company_name' => $this->faker->company(),
'address_1' => $this->faker->numberBetween(1, 16),
'address_2' => 'Heatherbell Cottages',
'address_3' => null,
'town' => 'Wick',
'county' => 'Caithness',
'postcode' => 'KW14YT',
'telephone_1' => $this->faker->regexify('07[1-57-9]{1}[0-9]{8}'),
'telephone_2' => $this->faker->regexify('07[1-57-9]{1}[0-9]{8}'),
'email' => $user->email,
'bank_name' => $this->faker->word(),
'bank_account_number' => $this->faker->numberBetween(11111111, 99999999),
'bank_sort_code' => $this->faker->numberBetween(111111, 999999),
'bank_iban' => $country ? $this->faker->iban($country->country_code) : null,
'bank_swift' => '',
'ccl_number' => null,
'data_protection_number' => $this->faker->numberBetween(11111111, 99999999),
'currency' => $country ? $country->currency_code : 'GBP',
'notes' => ''
];
}
}
You might have a belongsTo relation in the Company model.
You can use code like this.
$company = Company::factory()
->for(User::factory()->state([
'name' => 'User name',
]))
->create();
OR
$user = User::factory()->create();
$company = Company::factory()
->for($user)
->create();
https://laravel.com/docs/9.x/eloquent-factories#belongs-to-relationships

Refractoring Laravel 7 Factory to Laravel 8

I am having a hard to figuring out how to refractor a factory in Laravel 7 to Laravel 8. Below is the original factory in L7 and the L8 version below is the one I've tried refactoring. I know the $factory->define is wrong and this is where I am stuck.
Laravel 7 Version
<?php
/** #var \Illuminate\Database\Eloquent\Factory $factory */
use App\Login;
use Faker\Generator as Faker;
$factory->define(Login::class, function (Faker $faker) {
$randomDateTime = $faker->dateTimeBetween('-6 hours', 'now');
return [
'user_id' => factory(App\User::class),
'tenant_id' => factory(App\Tenant::class),
'created_at' => $randomDateTime,
'updated_at' => $randomDateTime,
];
});
Laravel 8 Version
<?php
namespace Database\Factories;
use App\Models\Login;
use App\Models\Tenant;
use App\Models\User;
use Faker\Generator as Faker;
$factory->define(Login::class, function (Faker $faker) {
$randomDateTime = $faker->dateTimeBetween('-6 hours', 'now');
return [
'user_id' => User::factory(),
'tenant_id' => Tenant::factory(),
'created_at' => $randomDateTime,
'updated_at' => $randomDateTime,
];
});
You have two options here:
Use old factories
If you don't have the time for properly refactoring your factories, you can still use your old Laravel 7 factories by pulling in the laravel/legacy-factories package:
composer require laravel/legacy-factories
Refactor your factories
Laravel 8 factories are now classes. Previously, you would call a define method on the $factory object, passing it the class and a closure that would return the factory definition like so:
<?php
use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
];
});
In Laravel 8, the same would be written as follows and be put in database/factories/UserFactory.php for auto-discovery:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
];
}
}
As you see, factories are now separate classes that extend from the Illuminate\Database\Eloquent\Factories\Factory class.
Additionally, you need to use the Illuminate\Database\Eloquent\Factories\HasFactory trait on the Model to allow the discovery of factories for the Model. Once you add this trait, Laravel expects a Factory class by the name of the Model, suffixed with Factory in the database/factories folder.
You can read up on all that in the Defining Model Factories chapter of the database testing documentation.
In your specific case
The code you posted would need to be refactored and put in database/factories/LoginFactory.php:
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class LoginFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
$randomDateTime = $faker->dateTimeBetween('-6 hours', 'now');
return [
'user_id' => User::factory(),
'tenant_id' => Tenant::factory(),
'created_at' => $randomDateTime,
'updated_at' => $randomDateTime,
];
}
}
Then you need to use Illuminate\Database\Eloquent\Factories\HasFactory; in your Login.php model class and of course you would have do the same for the User and Tenant models, creating UserFactory.php and TenantFactory.php.

Laravel - Trying to get property "id" of non-object while using POST Method

Am writing an endpoint with Laravel using using. When I tested on postman using POST Method, I got this error:
ErrorException: Trying to get property 'id' of non-object in file C:\xampp\htdocs\testing-file\testing\app\Http\Controllers\ApiController.php on line 912
Controller
public function storeBilling(Request $request)
{
// $billing = Billing::create($request->all());
// return response()->json(['success' => $billing], $this-> successStatus);
$validator = Validator::make($request->all(), [
'network' => 'required'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
// Creating a record in a different way
$createBilling = Billing::create([
'user_id' => $request->user()->id,
'network' => $request->network,
'sender' => $request->sender,
'recipient' => $request->recipient,
'message' => $request->message,
'amount' => $request->amount,
'billing_type' => $request->billing_type,
]);
return new BillingResource($createBilling);
}
Model
class Billing extends Model
{
protected $table = 'billing';
protected $fillable = [
'network' ,
'sender',
'recipient',
'message',
'timestamp',
'created_at',
'updated_at',
'amount',
'billing_type',
'user_id',
'service_name',
'package',
'email',
'user_id'
];
public function user() {
return $this->belongsTo('App\User');
}
}
Resource
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Billing;
class BillingResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'network' => $this->network,
'sender' => $this->sender,
'recipient' => $this->recipient,
'message' => $this->message,
'amount' => $this->amount,
'billing_type' => $this->billing_type,
'email' => $this->email,
'user' => $this->user,
'service' => $this->service,
'package' => $this->package,
// Casting objects to string, to avoid receive create_at and update_at as object
'timestamp' => (string) $this->timestamp,
'created_at' => (string) $this->created_at,
'updated_at' => (string) $this->updated_at
];
}
}
If I use this POST Method:
http://localhost/testing-file/stesting/api/storeBilling?network=100
It suppose to post into the database, but I got this error:
ErrorException: Trying to get property 'id' of non-object in file C:\xampp\htdocs\testing-file\testing\app\Http\Controllers\ApiController.php on line 912
'user_id' => $request->user()->id
Your error is saying that $request->user() is not an object, so you cannot access its parameters using object notation, e.g. ->id.
If you dd($request->user) you may see that you are not getting what you thought you were getting - it may be an array, or it may not be the right value at all.
If it is an array, you can access the value like $request['user']['id']. It really depends what you are passing in your POST request.
$request->user()->id is incorrect.
If you want the current user you can use Auth::user().
In the beginning of your question you said you are trying to build an endpoint using Lravel ..
Postman will not have access to the user object unless authenticated, if authenticated then this should work ::
$request->user()->id or Auth::user()->id or $request["user"]["id"]
on you
public function storeBilling(Request $request)
You write $createBilling = Billing::create([
'user_id' => $request->user()->id, and this create error.
Or is preferable to have $createBilling = Billing::create([
'user_id' => Auth::user()->id, to find the id of the user authentificate.
don't forget to add use Auth; at the beginning of the controller
Going through a same Hassle it's happening because relationship finding its relation with billing table but it did not find so giving this error please check your database have related entry's and try again and make sure you have right relationship with table.

Using Fractal's $defaultIncludes

I am trying to use a $defaultIncludes() and am getting an exception --
ErrorException in ViewoptionTransformer.php line 8:
Argument 1 passed to App\Transformers\ViewoptionTransformer::transform() must be an instance of App\Viewoption, boolean given
Following the tutorial (http://laravelista.com/build-an-api-with-lumen-and-fractal/) except I am using Laravel 5.1 not Lumen:
in User model, I have the hasOne relationship with Viewoption called viewoptions
In the UsersController, I eager load viewoptions
public function index(Manager $fractal, UserTransformer $userTransformer)
{
$records = User::with(['locations', 'viewoptions'])->get();
$collection = new Collection($records, $userTransformer);
$data = $fractal->createData($collection)->toArray();
return $this->respondWithCORS($data);
}
In the UserTransformer, I have the $defaultInclude and the includes method
protected $defaultIncludes = ['viewoptions'];
`public function transform(User $user)
{
return [
'id' => $user->id,
'name' => $user->name,
'is_active' => (boolean)$user->is_active,
'is_admin' => (boolean)$user->is_admin,
'is_manager' => (boolean)$user->is_manager,
'role_id' => (integer) $user->role_id,
'email' => $user->email,
'phone' => $user->phone,
'full_sidebar' => (boolean)$user->full_sidebar
];
}
public function includeViewoptions(User $user)
{
$viewoptions = $user->viewoptions;
return $this->collection($viewoptions, new ViewoptionTransformer);
}`
Have a ViewoptionTransformer
`
use App\Viewoption;
use League\Fractal\Resource\Collection;
use League\Fractal\TransformerAbstract;
class ViewoptionTransformer extends TransformerAbstract {
public function transform(Viewoption $item)
{
//return $item;
return [
'id' => $item->id,
'user_id' => $item->user_id,
'voAgency' => (boolean)$item->voAgency,
'voBalanceDue' => (boolean)$item->voBalanceDue,
'voCloseDate' => (boolean)$item->voCloseDate,
'voCommitTotal' => (boolean)$item->voCommitTotal,
'voDistributor' => (boolean)$item->voDistributor,
'voDueDate' => (boolean)$item->voDueDate,
'voFeePercentage' => (boolean)$item->voFeePercentage,
'voRegion' => (boolean)$item->voRegion,
'voSeason' => (boolean)$item->voSeason,
];
}
}`
Worked with these and slight variations of these throughout the day yesterday and I can't rid myself of that exception.
Not all of your users.id corresponds to some viewoptions.user_id.
Just check it:
$records = User::with(['locations', 'viewoptions'])->get();
dd($records);
some viewoptions will be null or false or just undefined
Instead of using collection use item like so
public function includeViewoptions(User $user){
$viewoptions = $user->viewoptions;
return $this->item($viewoptions, new ViewoptionTransformer);
}`
I had a similar issue today, all my other uses of transformers had been with hasMany relationships. I was always instantiating the transformer with a collection of objects.
However, when using a transformer with a belongsTo relationship and instantiating the transformer with only one object (similar to how you are passing only one object from a hasOne relationship) I would get the same boolean given error.
In the end I solved the issue by using 'item' instead of 'collection' when instantiating the transformer.
Within your includeViewoptions function
Instead of using
return $this->collection($viewoptions, new ViewoptionTransformer);
try
return $this->item($viewoptions, new ViewoptionTransformer);

Laravel5 : Calling a static method on eloquent model not working

I have a static method in User model.
namespace Tol;
...
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
...
public static function signup(array $data)
{
$user = new User([
'email' => $data['email'],
'password' => Hash::make($data['password']),
'username' => $data['username'],
'type' => $data['type'],
]);
$user->save();
if ($user && $user->id) {
$profile = new UserProfile([
'first_name' => trim($data['first_name']),
'last_name' => trim($data['last_name']),
'gender' => $data['gender'],
]);
$user->profile()->save($profile);
EmailVerification::sendTo($user, 'signup');
}
return $user;
}
...
}
And I'm trying to call call this method simply from my controllers.
like this
$user = User::signup($input);
And it throws error like this:
I don't know why it is referring it as a method on the Builder class. The code is very simple and everything was working when it was Laravel 4.
Please help.
thanks
your code should have no problem, im afraid the problem is in your auth.php file, please ensure
'model' => 'App\User',
is set it to your model file in your case
'model' => 'Tol\User',
and to ensure your calling the right file you might want to give this a try
\Tol\User::signup($array);

Categories