Laravel model factories using same seed for testing purposes - php

I am trying to set up a default seed for Faker in Laravel which is normally achieved in this way (not in Laravel):
<?php
$faker = Faker\Factory::create();
$faker->seed(1234);
according to Faker's GitHub page.
I am trying to do this so that can I get the same data generated each time so that I can write some unit tests but I have no idea how to do that in Laravel. I've checked Laravel's documentation and tried googling the issue but I found nothing.

Here's how to do apply the seed to Faker in Laravel 5.
Inside your app/database/factories directory, create a new file. I called mine SeedFactory.php.
<?php
$factory->faker->seed('1');
That's it!
Now you have consistent random data for your unit testing!
NB: If you only have one or two factories, you could append that line to an existing factory file.
Here's why it works.
When Laravel processes all the files in the app/database/factories directory, it executes them straightaway. The $factory object passed around is an instance of Illuminate\Database\Eloquent\Factory.php, which keeps with it it's own internal Faker\Generator instance.
Also, you won't need to worry about the naming of the file or execution order, because this will get fired before any of the factory callbacks (assuming you did it as instructed in the Laravel docs).

it is easy. Just define a factory. Let's have a look at the default factory shipped
with laravel 5.5
File: database/factories/ModelFacotry.php
<?php
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| Here you may define all of your model factories. Model factories give
| you a convenient way to create models for testing and seeding your
| database. Just tell the factory how a default model should look.
|
*/
/** #var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(App\User::class, function (Faker\Generator $faker) {
static $password;
// Add this line to original factory shipped with laravel.
$faker->seed(123);
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('secret'),
'remember_token' => str_random(10),
];
});
Then use tinker to test it:
yudu#YududeMacBook-Pro ~/demo> php artisan tinker
Psy Shell v0.8.1 (PHP 7.1.8 — cli) by Justin Hileman
>>> $user = factory(App\User::class)->make()
=> App\User {#880
name: "Jessy Doyle",
email: "jalen86#example.net",
}
>>> $user = factory(App\User::class)->make()
=> App\User {#882
name: "Jessy Doyle",
email: "lbode#example.org",
}
Laravel Docs:
how to define and use factory
Seeding

Related

How to over ride a Laravel class with my own?

So I am working with Laravel 5.2 and I trying to shift to SQS as my queue serice.
There was a bug in 5.2 in QueueSqs.php which was fixed here -> https://github.com/illuminate/queue/blob/5.6/SqsQueue.php in 5.6
Now I am not sure I can upgrade to 5.6 yet, because a lot of things are working with 5.2 and I don't want to break anything.
But I am sure I can somehow use this class in my code from 5.6 and tell Laravel to use it somehow. But I don't know how to.
I haven't checked if this worked back in Laravel 5 or in class extends, but in newer versions Laravel generally tries to resolve the class from its container and by binding the original FQNS of the class to your your custom SqsQueue, it instead returns yours everytime the original is used:
class AppServiceProvider extend ServiceProvider
{
public function register()
{
$this->app->bind(\Illuminate\Queue\SqsQueue::class, function ($app) {
return new CustomSqsQueue();
});
}
}
Well, actually, instead of overriding the original class and re-binding it for Laravel to use you could also add your own custom queue connector and use this instead of Laravels native SQS queue connector:
To add a new connector for the queue to use, open up a service provider and add the following code to the boot
public function register()
{
$manager = $this->app['queue'];
$manager->addConnector('sqs-custom', function() {
return new CustomSqsQueue;
});
}
Then, in the queue.php config, add a new connection based on the original SQS connection, but with the driver name you chose in the first parameter or addConnector in the service provider. The key of the array ist the name of the connection you use to define your queues:
'sqs-custom' => [
'driver' => 'sqs-custom',
'key' => 'your-public-key',
'secret' => 'your-secret-key',
'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id',
'queue' => 'your-queue-name',
'region' => 'us-east-1',
],
Again, this is untested but should be able to work in Laravel 5.2.

Laravel Factory State- Unable to locate factory

I have a problem when executing the factory, i have used the factory state for factories but it will give me an error when i execute the factory
https://laravel.com/docs/5.6/database-testing#factory-states
I have this UserFactory.php which contains the code below.
<?php
use Faker\Generator as Faker;
$factory->state(App\User::class,'suggestor', function (Faker $faker) {
return [
'FirstName'=>$faker->firstName,
'LastName'=>$faker->lastName,
'Username'=>$faker->userName,
'password'=>bcrypt('123asd!##'),
'Email'=>$faker->email,
'AccountType'=>0,
];
});
i am using tinker to execute the factory commands and tried different syntax but it really does not solve the problem.
>>> factory(User::class, 1)->states('suggestor')->make();
[!] Aliasing 'User' to 'App\User' for this Tinker session.
InvalidArgumentException with message 'Unable to locate factory with name [default] [User].'
>>> factory(App\User::class, 1)->states('suggestor')->make();
InvalidArgumentException with message 'Unable to locate factory with name [default] [App/User].'
>>> factory(\App\User::class, 1)->states('suggestor')->make();
InvalidArgumentException with message 'Unable to locate factory with name [default] [App/User].'
>>> factory('App\User')->states('suggestor')->make();
InvalidArgumentException with message 'Unable to locate factory with name [default] [App/User].'
>>> factory('App\User',1)->states('suggestor')->make();
InvalidArgumentException with message 'Unable to locate factory with name [default] [App/User].'
>>>
i hope there's anyone out there can help me.
Update:
I have tried running it on database seed but i think it's still the same error.
but when i tried on the other model it seems fine. i think the problem is on the User model which comes out of the box by laravel and note that i haven't change anything on the user model except the model attributes.
error produce by seeder
As the error states, you need a default factory. Please compare the following two:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->safeEmail,
'password' => bcrypt(str_random(10)),
'remember_token' => $faker->randomNumber(),
];
});
$factory->state(App\User::class, 'test_state', function (Faker\Generator $faker) {
return [
'name' => 'Namoshek',
'email' => 'namoshek#example.com',
];
});
The first definition is the default factory for users, when not giving a state. You can call them with factory(App\User::class, 10)->create() where 10 is optional to give the number of models to create.
You can also chain ->states('test_state') after the call to factory():
factory(App\User::class)->states('test_state')->create(), which will first run the default factory and then apply the changes defined by the given state on the model. But you always need a default factory, otherwise the system doesn't know where and what to apply the state to.
By the way, there is a difference between ->create() and ->make(). The latter does only create the models without persisting them in the database, whereas the first one persists them. So ->create() is equivalent to ->make()->save().
sometimes it also happens that factory works fine in web routes and tests but in tinker it behaves as it is mentioned above. In that case you can try to clear laravel application cache. here are the commmands.
php artisan cache:clear
php artisan config:clear
php artisan route:clear
this will clear all the caches. then i could create model instances using factory.
>>> factory(User::class)->create() // or
>>> factory(Book::class)->create()

PHPUnit Laravel Hash not available

I have a Unit test in Laravel for testing an API call that looks like this, however I am getting the following runtime error when running it:
RuntimeException: A facade root has not been set.
I'm creating a user in the setup method, with the intent to delete it again in the tearDown() method, then run my auth test.
Firstly, is there a better way of doing what I want? For example Mocking a user without touching the database? And secondly, how do I set a 'facade root' or what does that error mean exactly? I've tried not bothering to hash that particular field for the purposes of creating a Dummy user, but the error then seems to move to the model, where (again) the Hash facade class is used.
Is there any additional steps to setup the environment so these facades can be used in testing?
Thanks in advance.
use Illuminate\Support\Facades\Hash;
/*
* Make sure the structure of the API call is sound.
*/
public function testAuthenticateFailed()
{
$this->json('POST', $this->endpoint,
[ 'email' => 'test#test.com',
'password' => 'password',
])
->seeJsonStructure([
'token'
]);
}
//create a user if they don't already exist.
public function setup()
{
$user = User::create([
'company_id' => 9999,
'name'=>'testUser',
'email' => 'test#test.com',
'password' => 'password',
'hashed_email' => Hash:make('test#test.com'),
]);
}
Try to use this instead:
\Hash::make('test#test.com'),
It's a good idea to use bcrypt() global helper instead of Hash::make()
Also, add this to setUp() method:
parent::setUp();
You could use the DatabaseMigrations or DatabaseTransactions trait that comes with Laravel so you don't have to delete the User manually.
You could add a Mutator to your User class, which will automatically hash the password when a User is created.
// https://laravel.com/docs/5.3/eloquent-mutators
public function setPasswordAttribute($value) {
$this->attributes['password'] = bcrypt($value);
}

Integrate authentication to existing Laravel project and users database

I am a totally newbie to Laravel, but after following lots and lots of guide, my project was really going well.
Now, I have a user table named UsersTable, which is going to store both Username and Password.
However, after trying php artisan make:auth and a lot of Googling, I found no clear, exact way of defining both the User table name, and the Username-Password field used for authentication.
I've tried the original Email-Password authentication on the table named users on another project I created for trying this, and it works like a charm. I believe that as Laravel already provide the authentication system, why reinvent the wheels when (I believe) there's a way to customise it.
Thanks in advance! :D
Use php artisan make:auth and then :
To change the default table from users which uses the User model
you have to navigate to : config/auth.php
And make these changes
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\UsersTable::class, //put the right model name here or you can use database driver
],
],
To change the table for an eloquent model you can do this
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class UsersTable extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'UsersTable';
}
To change the default login credential from email to user name , just add this to the Authcontroller generated by laravel
class AuthController extends Controller
{
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
protected $redirectTo = '/home';
protected $username = 'username'; // you can put whatever column you want here from your table
Now
edit laravel generated Views and change the email input name to name="username" then extend the main.blade.php, do some styling and you are good to go i guess
EDIT : Dont forget to change the validation stuff
Laravel basic authentication system is not the fastest way to create your own auth method ... I suggest to you Sentinel Package to customize your authentications ...
for example to authenticate your user by using user name you can do like this :
Sentinel::authenticate(array(
'username' => 'john.doe',
'password' => 'foobar',
));
for me it's the fastest way to customize your authentication method i hope that will help you.

Change Faker Locale in Laravel 5.2

Is there a way to specify the Faker locale in the database/factories/ModelFactory.php file ? Here is my non functioning attempt at doing so >,<
$factory->define(App\Flyer::class, function (Faker\Generator $faker) {
// What is the correct way of doing this?
$faker->locale('en_GB');
return [
'zip' => $faker->postcode,
'state' => $faker->state,
];
});
Thanks for reading!
Faker locale can be configured in the config/app.php configuration file. Just add the key faker_locale.
e.g.: 'faker_locale' => 'fr_FR',
See also my PR to document that previously undocumented feature: https://github.com/laravel/laravel/pull/4161
THIS ANSWER IS ONLY VALID FOR LARAVEL <=5.1 OR WHEN YOU WANT TO USE MANY DIFFERENT LOCALES see this answer for a solution in Laravel >=5.2.
To use a locale with Faker, the generator needs creating with the locale.
$faker = Faker\Factory::create('fr_FR'); // create a French faker
From the faker documentation:
If no localized provider is found, the factory fallbacks to the default locale (en_EN).
Laravel by default, binds the creation of a faker instance in the EloquentServiceProvider. The exact code used to bind is:
// FakerFactory is aliased to Faker\Factory
$this->app->singleton(FakerGenerator::class, function () {
return FakerFactory::create();
});
It would appear that Laravel has no way to modify the locale of the faker instance it passes into the factory closures as it does not pass in any arguments to Faker.
As such you would be better served by using your own instance of Faker in the factory method.
$localisedFaker = Faker\Factory::create("fr_FR");
$factory->define(App\Flyer::class, function (Faker\Generator $faker) {
// Now use the localisedFaker instead of the Faker\Generator
// passed in to the closure.
return [
'zip' => $localisedFaker->postcode,
'state' => $localisedFaker->state,
];
});
I prefer to use it:
$fakerBR = Faker\Factory::create('pt_BR');
$factory->define(App\Flyer::class, function (Faker\Generator $faker) use (fakerBR) {
return [
'name' => $fakerBR->name,
'cpf' => $fakerBR->cpf,
'zip' => $faker->postcode,
'state' => $faker->state,
];
});
Late in the party, but after some research I've found this in faker documentation:
[...] since Faker starts with the last provider, you can easily override existing formatters: just add a provider containing methods named after the formatters you want to override.
That means that you can easily add your own providers to a Faker\Generator instance.
So you can do something like this
$faker->addProvider(new Faker\Provider\pt_BR\Person($faker));
Just before return [] and then use specific providers, like (in this case) $faker->cpf;
Tested on Laravel 5.3
More info on Faker documentation
#IvanAugustoDB, there is a another form of doing that. When Laravel initalize faker, it can be constructed on another locale, just create a Service Provider and put the following snippet inside it.
use Faker\Generator as FakerGenerator;
use Faker\Factory as FakerFactory;
$this->app->singleton(FakerGenerator::class, function () {
return FakerFactory::create('pt_BR');
});
$factory->define(App\User::class, function (Faker\Generator $faker) {
$faker->addProvider(new Faker\Provider\ng_NG\Person($faker));
$faker->addProvider(new Faker\Provider\ng_NG\PhoneNumber($faker));
...
in the above code, "ng_NG" is for Nigeria and can be replaced with any other locale.
To my knowledge, you would have to specify Person, PhoneNumber and others depending on what you have in your vendor\fzaninotto\faker\src\Faker\Provider folder. However if the provider you intend using isn't available, then it will resolve back to using "en".
This works like charm for me, and it should work for you too.
This answer is valid just for Laravel 5.4 and greater:
Since this pull, you can just use 'faker_locale' as a variable in your app config file. It just works really good.
this is the link for all providers that used in faker
for arabic lang example
use Faker\Factory as Faker; ### in the head off class
$faker = Faker::create();
$faker_ar = Faker::create('ar_SA');
for ($i = 0; $i < 10; $i++) {
DB::table('categories')->insert([
'name' => $faker->name,
'name_ar' => $faker_ar->name,
'created_at' => now(),
'updated_at' => now(),
]);
}
If you are using multiple languages for the same table and can't use local
you can use: shuffleString
'name'=>$faker->shuffleString('abddefhig')
'name_ar'=>$faker->shuffleString('البتثجحخدزسش')

Categories