Simple Laravel 5 Seeding: Closure object cannot have properties - php

I am trying to seed some files, yet I get:
[Error Exception] Closure object cannot have properties
Not sure what is wrong, since I do very basic seeding.
Here are my files:
all.php in tests/factories/all.php
$factory('App\User', [
'name' => $faker->name,
'email' => $faker->email,
'password' => password_hash('000400', PASSWORD_DEFAULT)
]);
This is the command I am using:
php artisan db:seed --class="UserTableSeeder"
This is my UserTableSeeder:
public function run()
{
// User::create([
// 'name' => 'Rainbow Warrior',
// 'email' => 'email#exmaple.org',
// 'password' => password_hash('123456', PASSWORD_DEFAULT)
// ]);
TestDummy::times(20)->create('App\User');
}

I had a typo inside my all.php that looked like this:
$factory('App\Comment', [
'user_id' => 'factory:App\User',
'question' => $factory->sentence,
'answer' => $factory->text
]);
If you look closely, you will notice, that instead of $faker, I wrote $factory. Well... fair enough.

Related

Testing Laravel Password Reset

I'm implementing the password reset functionality described in the Laravel docs at https://laravel.com/docs/8.x/passwords. My method for resetting passwords is as follows:
public function doPasswordReset(Request $request)
{
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => 'required|min:8|confirmed',
]);
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user, $password) {
$user->forceFill([
'password' => Hash::make($password)
]);
//remember token not needed
//->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
}
);
return $status === Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status))
: back()->withErrors(['email' => [__($status)]]);
}
My test for this method is:
/** #test */
public function the_user_can_update_their_password()
{
ParentUser::factory()->create([
'email' => 'user#domain.com',
'password' => Hash::make('oldpassword')
]);
$token = Password::createToken(ParentUser::first());
Password::shouldReceive('reset')
->once()
->withSomeofArgs([
'email' => 'user#domain.com',
'password' => 'newpassword',
'password_confirmation' => 'newpassword',
'token' => $token
])
->andReturn(Password::PASSWORD_RESET);
$response = $this->post(route('password.update'), [
'email' => 'user#domain.com',
'password' => 'newpassword',
'password_confirmation' => 'newpassword',
'token' => $token
]);
$response->assertRedirect(route('login'));
//failures from here
$this->assertEquals(Hash::make('newpassword'), Hash::make(ParentUser::first()->password));
$this->assertNotEquals(Hash::make('oldpassword'), Hash::make(ParentUser::first()->password));
Event::fake();
Event::assertDispatched(PasswordReset::class, ParentUser::first());
}
My issue is that the last three assertations fail. I realize that this is happening because my mock is not calling the closure to effect the password change and raise the event. So my question is whether it is possible to mock some arguments of a function call.
One solution I am thinking is to factor out the closure into its own method and test that separately. In the absence of anything else, I think this may be the only way.
Props to #tim-lewis and #apokryfos who both provided answers to help. The following is the amended test which now passes.
#apokryfos stated that mocking the call shouldn't happen as we need to actuallly effect the password change.
#tim-lewis in his answer showed that a hash needs to be checked with the Hash::check() method
/** #test */
public function the_user_can_update_their_password()
{
ParentUser::factory()->create([
'email' => 'user#domain.com',
'password' => Hash::make('oldpassword')
]);
$token = Password::createToken(ParentUser::first());
Event::fake();
$response = $this->post(route('password.update'), [
'email' => 'user#domain.com',
'password' => 'newpassword',
'password_confirmation' => 'newpassword',
'token' => $token
]);
dump(ParentUser::first()->password);
$response->assertRedirect(route('login'));
$this->assertTrue(Hash::check('newpassword', ParentUser::first()->password));
Event::assertDispatched(PasswordReset::class);
}
Hash::make() doesn't generate the same Hash for any given value. Take a look:
Hash::make('newpassword') == Hash::make('newpassword')
// false
Since this is false, I would expect assetEquals() to fail here, since they values do not equal each other. Instead, take a look at the Hash::check() method:
Hash::check('newpassword', Hash::make('newpassword'))
// true
You can use assertTrue() against it instead, something like:
$this->assertTrue(Hash::check('newpassword', ParentUser::first()->password));
$this->assertFalse(Hash::check('oldpassword', ParentUser::first()->password));
Sidenote, ->password should already be Hashed, so there's no need to wrap that in another Hash::make(), as you'd then have a "Hash-of-a-hash", which would fail the Hash::check().

Laravel Factory - How to make meaningful dummy data with a Factory

I want to make gender, age, and nationality of Korea and Japan, but I don't know what to do.
$factory->define(User::class, function (Faker $faker) {
$gender = $faker->randomElement(['men', 'women']);
$address = $faker->randomElement(['seoul', 'fucuoka', 'tokyo']);
$country = $faker->randomElement(['korea', 'japan']);
$age = rand(3, 100);
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret,
'country' => $country,
'address' => $address,
'age' => $age,
'gender' => $gender,
'remember_token' => str_random(10),
];
});
Did you think about setting the locale to Korean via Laravel? Then Faker will produce a Korean address etc.
Add this to your app.php file in the config folder:
'faker_locale' => 'ko_KR',
All locales available can be found here:
https://faker.readthedocs.io/en/master/locales/ko_KR.html
add this line(key) in your config/app.php file
'faker_locale' => 'ko_KR',
and run config:cache command
php artisan config:cache
other wise create object like that if don't want to globally
Faker\Factory::create('ko_KR')
and in your should be like that addd
$factory->define(User::class, function (Faker\Factory::create('ko_KR') $faker)

Laravel generate object using model factory with parameter

I have this model factory that generates new contact, it uses random company_id:
$factory->define(App\Models\Contact::class, function (Faker\Generator $faker)
{
$company_id = Company::all()->random()->id;
return [
'firstname' => $faker->firstName,
'lastname' => $faker->lastName,
'phone' => $faker->phoneNumber,
'email' => $faker->email,
'company_id' => $company_id,
'lead_id' => \App\Models\Lead::where('company_id', $company_id)->get()->random()->id,
];
});
It is ok when I use it in seeds:
factory(App\Models\Contact::class)->create();
But for testing I need somehow to pass $company_id to factory, create contact for concrete company_id (I know that I can do ->create(['company_id', $company_id])) but this will rewrite only company_id from Contact.
When I select lead_id, I also need to know current company_id.
How to pass company_id to factory as parameter?
Try to use this example:
$factory->define(App\Models\Contact::class, function ($faker, $params) {
$company_id = $params['company_id'];
....
});
and this to make a new object:
$newContact = factory(App\Models\Contact::class)->make(['company_id' => $current_company_id]);
Depends on your Laravel version it will be different.
For laravel 5.1
https://github.com/laravel/framework/issues/9245
You will need to check if is passed manually
// Testcase
$company = factory(App\Models\Company::class)->create();
factory(App\Models\Contact::class)->create(['company_id' => $company->id]);
// Factory
$factory->define(App\Models\Contact::class, function (Faker\Generator $faker, $attribues) {
// Manually check if id is passed
$company_id = (isset($attribues['company_id'])) ?: Company::all()->random()->id;
return [
'firstname' => $faker->firstName,
'lastname' => $faker->lastName,
'phone' => $faker->phoneNumber,
'email' => $faker->email,
'company_id' => $company_id,
'lead_id' => \App\Models\Lead::where('company_id', $company_id)->get()->random()->id,
];
});
For Laravel 5.2 and above you can simply pass id
https://laravel.com/docs/5.5/database-testing#relationships
// Testcase
$company = factory(App\Models\Company::class)->create();
factory(App\Models\Contact::class)->create(['company_id' => $company->id]);
// Factory
$factory->define(App\Models\Contact::class, function (Faker\Generator $faker, $attribues) {
// Manually check if id is passed
$company_id = (isset($attribues['company_id'])) ?: Company::all()->random()->id;
return [
'firstname' => $faker->firstName,
'lastname' => $faker->lastName,
'phone' => $faker->phoneNumber,
'email' => $faker->email,
'company_id' => function(){
return factory(\App\Models\Company::class)->create()
},
'lead_id' => \App\Models\Lead::where('company_id', $company_id)->get()->random()->id,
];
});
So, for your case, get your random company_id first and pass it to factory. Depends on your Larval version change the ContactModel factory. Or if you have relation that you can query, you can do it as well.
// If you have a relation
$contact = factory(App\Models\Contact::class)->create();
$company_id = $contact->company->id;

Laravel 5.3: UnitTest on API - multiple function lead to error

Hope someone can help me out here. I bootstrapped a Laravel 5.3 application and I'm writing JUnitTest in order to verifiy my API.
However, as long as I create only one function in the Test Class everything works fine:
class MobileAppTest extends TestCase {
function __construct()
{
parent::setUp();
}
public function testUserLogin()
{
$this->json('POST', '/mobile/auth', ['username' => 'empty', 'password' => 'demo'])
->seeJson([
'success' => true
]);
$this->json('POST', '/mobile/auth', ['username' => 'wrongUser', 'password' => 'demo'])
->seeJson([
'success' => false,
'state' => 2
]);
}}
When I start to add another function like this:
class MobileAppTest extends TestCase{
function __construct()
{
parent::setUp();
}
public function testUserLogin()
{
$this->json('POST', '/mobile/auth', ['username' => 'empty', 'password' => 'demo'])
->seeJson([
'success' => true
]);
$this->json('POST', '/mobile/auth', ['username' => 'wrongUser', 'password' => 'demo'])
->seeJson([
'success' => false,
'state' => 2
]);
}
public function testSecond()
{
}}
Suddenly the parameters of the HTTP POST call become empty. I have no idea why this is happening. On the server-log there is the following message poping up:
[2017-05-10 11:09:15] testing.INFO: array (
)
[2017-05-10 11:09:15] testing.ERROR: ErrorException: Undefined index: username
in app/Http/Controllers/Controller.php:36
But on the second call the parameters are present:
[2017-05-10 11:09:15] testing.INFO: array (
'username' => 'wrongUser',
'password' => 'demo',
)
Thanks for helping me out here.

Laravel 5 Seeding

I'm following the docs to seed the users table, which shows User::create being used
class UserTableSeeder extends Seeder {
public function run()
{
DB::table('users')->delete();
User::create([
'username' => 'test',
'firstname' => 'Test',
'lastname' => 'Test',
'email' => 'test#domain.com',
'password' => Hash::make('test'),
'role' => 'user'
]);
}
}
But it keeps saying:
PHP Fatal error: Class 'User' not found in /home/vagrant/projects/roopla/database/seeds/UserTableSeeder.php on line 17
I thought maybe I need to make:model using artisan for User, but it already exists. Can anyone point me in the right direction I'm only just starting out with Laravel and piecing it together through Laracast and the docs, but there's no Laracast on seeding for 5.0. It doesn't seem like you can generate seeds now as artisan doesn't recognize generate:seed
That's because in Laravel 5 User model is in App namespace whereas your seeder is in global namespace.
You should use:
\App\User::create([
'username' => 'test',
'firstname' => 'Test',
'lastname' => 'Test',
'email' => 'test#domain.com',
'password' => Hash::make('test'),
'role' => 'user'
]);
or at the beginning of seeder file:
use App\User;
and now you can use the code you showed
You need to either import your user model at the top of the seeder with use App\User; or just reference it via \App\User::create.... You need to reference the User model through it's namespace.

Categories