For laravel testing environment, Laravel gate is not working. In phpunit.xml
file, I am using sqlite connection and :memory: as database.
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
In AuthServiceProvider, I am defining gate as in this code below.
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
foreach($this->getPermissions() as $permission) {
$gate->define($permission->name, function($user) {
$user->hasRole($permission->roles);
});
}
}
protected function getPermissions() {
return Permission::with('roles')->get();
}
So, when ever I run phpunit. It shows error no such table: permissions (SQL: select * from "permissions").
So, please guide how can I define gate after migration for testing environment.
Try this :
foreach($this->getPermissions() as $permission) {
$gate->define($permission->name, function($user)use($permisson) {
$user->hasRole($permission->roles);
});
}
if you do not use use($permission) the $permission will be undefined...
Related
I am currently working on having a default image in a table instead of null. I already have an API that will put an image in that specific column (web_banner_profile) which is a POST method and a DELETE method that will make that column NULL, all of which using postman. I want to know how I can put a default image on all of the webinars table in the web_banner_profile.
This is the Banner Upload Controller:
<?php
namespace App\Http\Controllers;
use App\Models\Banner;
use Illuminate\Http\Request;
// use Illuminate\Support\Facades\Validator;
class BannerUploadController extends Controller
{
public function FileUpload(Request $request, $id)
{
$uploaded_files = $request->file->store('public/uploads/');
$webinar = Banner::find($id);
$webinar->web_banner_profile = $request->file->hashName();
$results = $webinar->save();
if($results){
return ["result"=>"Image Added"];
}else{
return ["result"=>"Image Not Added"];
}
return ["result"=>"$uploaded_files"];
}
public function DeleteBanner($id)
{
$webinar = Banner::find($id);
if(is_null($webinar)){
return response()->json('Record not found!', 401);
}
$webinar->update(['web_banner_profile' => null]);
return response('Banner Deleted', 200);
}
}
This is the webinar table migration:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateWebinarTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('webinar', function (Blueprint $table) {
$table->id();
$table->string('web_title');
$table->text('web_description');
$table->dateTime('web_start_date_time')->nullable();
$table->dateTime('web_end_date_time')->nullable();
$table->string('status')->nullable();
$table->string('remarks')->nullable();
$table->string('web_banner_profile')->nullable();
$table->bigInteger('created_by')->unsigned()->nullable();
$table->bigInteger('updated_by')->unsigned()->nullable();
$table->string('web_link')->nullable();
$table->timestamps();
});
Schema::table('webinar', function(Blueprint $table) {
$table->foreign('created_by')->references('id')->on('admins');
$table->foreign('updated_by')->references('id')->on('admins');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('webinar');
}
}
Any type of help/suggestion would be greatly appreciated. Thank you in advance!
I would consider using one of the Eloquent model events, specifically the created event. Something like the following:
class Webinar extends Model
{
protected static function booted()
{
static::created(function ($webinar) {
$webinar->update(['web_banner_profile' => 'your-image.jpg']);
});
}
}
Then whenever a new Webinar is created, the created event will be triggered and your default web_banner_profile value will be added to that record.
You don't have to hard code the value your-image.jpg, you could obtain it from a config of env file if you didn't want it in your code base to (arguably) make changing the value easier.
I've created an observer to complete the user_id when a user adds a new department.
The created method in the DepartmentObserver:
public function created(Department $department)
{
//
if (auth()->check())
{
$department->created_by_user_id = auth()->id();
$department->save();
}
}
The model:
protected $fillable = [
'deptname',
'created_at',
'updated_at',
'deleted_at',
'created_by_user_id'
];
public function user()
{
return $this->belongsTo(User::class,'created_by_user_id');
}
Then registered the obeserver in the AppServiceProvider Class
public function boot()
{
//
Department::observe(DepartmentObserver::class);
}
I get the following error:
SQLSTATE[HY000]: General error: 1364 Field 'created_by_user_id' doesn't have a default value (SQL: insert into departments (deptname, updated_at, created_at)
I think the oberserver is not firing on the create event. I also tried composer dump and php artisan config:cache
and it didn't work. I'm on laravel 8 (Breeze, not Jetstream)
I intend to use in memory database for unit testing in laravel...
I added this lines to phpunit.xml,
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_CONNECTION" value="sqlite" />
<env name="DB_DATABASE" value=":memory:" />
</php>
And this is my BookTest.php file inside \tests\Feature\BookTest.php directory,
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class BookTest extends TestCase
{
use DatabaseMigrations;
public function test_books_can_be_created()
{
$user = factory(\App\User::class)->create();
$book = $user->books()->create([
'name' => 'The hobbit',
'price' => 10
]);
$found_book = $book->find(1);
$this->assertEquals($found_book->name, 'The hobbit');
$this->assertEquals($found_book->price, 10);
}
}
But when we tried to add vendor\bin\phpunit I get,
1) Tests\Feature\BookTest::test_books_can_be_created
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1 no such table: books (SQL: insert into "books" ("name",
"price", "user_id", "updated_at", "created_at") values (The hobbit,
10, 1, 2018-03-23 08:52:26, 2018-03-23 08:52:26))
it would be great help if you can help me on how to use in memory database in laravel unit testing.
You should
use RefreshDatabase
so your test should look like this
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class BookTest extends TestCase
{
use RefreshDatabase;
public function test_books_can_be_created()
{
$user = factory(\App\User::class)->create();
$book = $user->books()->create([
'name' => 'The hobbit',
'price' => 10
]);
$found_book = $book->find(1);
$this->assertEquals($found_book->name, 'The hobbit');
$this->assertEquals($found_book->price, 10);
}
}
source https://laravel.com/docs/5.5/database-testing#resetting-the-database-after-each-test
I setup a test database:
phpunit.xml:
<phpunit>
<!... other stuff ...>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_CONNECTION" value="sqlite_testing"/>
</php>
</phpunit>
database.php:
'connections' => [
'sqlite_testing' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
$this->call([
MyTableSeeder::class
]);
}
}
MyTableSeeder.php:
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use App\My\Model;
class MyTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
DB::table('model')->insert([
'id' => 1,
'created_at' => \Carbon\Carbon::now(),
'updated_at' => \Carbon\Carbon::now()
]);
/**
* create random data
*/
factory(Model::class, 50)->create();
}
}
ModelFactory.php:
<?php
use Faker\Generator as Faker;
$factory->define(\App\My\Model::class, function (Faker $faker) {
return [
'id' => $faker->unique()->randomNumber(),
'created_at' => $faker->dateTime('now'),
'updated_at' => $faker->dateTime('now')
];
});
MyTest.php:
<?php
namespace Tests\Unit;
use App\My\Model;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class MyTest extends TestCase
{
use RefreshDatabase;
public function testGettingModel() {
var_dump(Model::all()); // <-- returns a collection without any items
$model = Model::where('id', 1)->first();
$this->assertEquals('1', $model->id); // <-- trying to get property of non-object
}
}
So at the test run it seems the database is migrated by the trait but not seeded and thus nothing is returned.
The documentation does not state however (or I couldn't find it) how to seed the database upon testing.
It states how to manually seed by running "php artisan db:seed" but that's not working in a test obviously as the database doesn't exist anymore after the test. And I can't run it manually as the database doesn't exist before the test. Also that would make testing impractical.
Some examples state running the seeding before testing in the setup method of the test like so:
public function setUp() {
Artisan::call('db:seed');
}
But including this statement leads to the error:
RuntimeException : A facade root has not been set. (in /vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:218)
Alternatively running it like so:
public function setUp()
{
$this->artisan('db:seed');
}
Leads to:
Error : Call to a member function call() on null (in /vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithConsole.php:18)
How do I actually do this? Is there any fully working example anywhere? So far I couldn't find any :(
You have to call $this->seed(); in the setUp method. But you need to ensure to call the parents setUp method before or it will fail.
<?php
namespace Tests\Unit;
use App\My\Model;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class MyTest extends TestCase
{
use RefreshDatabase;
public function setUp()
{
parent::setUp();
$this->seed();
}
public function testGettingModel() {
$model = Model::where('id', 1)->first();
$this->assertEquals('1', $model->id); // will assert to true if the id actually exists
}
}
I have a Laravel 5.4 app which has models pointing to different database connections.
For example, I have User pointing to a MySQL database and then Company pointing to a PostgreSQL database (using the $connection variable).
Now, when I run PHPUnit I'd like the $connection variable to be replaced by what's specified in the phpunit.xml file, which is a SQLite in memory type of database.
How is that achievable?
Most answers are changing production code, which I don't like.
Since \Illuminate\Contracts\Foundation\Application is available in your tests, let's use it!
<?php
declare(strict_types=1);
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Company;
class CompanyFeatureTest extends TestCase
{
/**
* #return void
*/
protected function setUp(): void
{
parent::setUp();
$this->app->bind(Company::class, function () {
return (new Company())->setConnection(config('database.default'));
});
}
}
Whenever your Company class is called, we give back a manipulated one.
In this one we have changed the $connection property.
If you have the following in your phpunit.xml:
<server name="DB_CONNECTION" value="sqlite"/>
The value of config('database.default') will be sqlite.
More info about binding can be found here: https://laravel.com/docs/5.8/container#binding
I'd rather not touch the production code and instead use the service container to create test-specific services.
In this case, if you want all your models to use the same default testing connection:
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
$fakeManager = new class ($app, $app['db.factory']) extends DatabaseManager {
public function connection($name = null) {
return parent::connection($this->getDefaultConnection());
}
};
$app->instance('db', $fakeManager);
Model::setConnectionResolver($fakeManager);
return $app;
}
(This overrides the CreatesApplication trait, you could instead place this code anywhere between when the app is bootstrapped and when the migration command is called).
(Also note this is using PHP 7 inline anonymous classes. You could also define a fake db manager as a separate class).
Off the top of my head you could move the connection names to the .env file
in your model:
public function __construct(array $attributes = [])
{
$this->connection = env('MY_CONNECTION');
parent::__construct($attributes);
}
in your .env file
MY_CONNECTION=mysql
in phpunit.xml
<env name="MY_CONNECTION" value="sqlite"/>
As mentioned before, you first need to set the connection in each Model.
So, you setup the connections in the database config file, set the values in the .env file and use these in the Model's constructors.
For testing, you could also do this.
Add the testing connection to the config/database.php file and then use an overriding env file.
Create an additional env file, name it something like .env.testing.
So, in your .env file you will have:
CONNECTION_MYSQL=mysql
CONNECTION_POSTGRESS=postgress
Then in the .env.testing file you can have:
CONNECTION_MYSQL=test_sqlite
CONNECTION_POSTGRESS=test_sqlite
Finally to load this env file when testing, go to CreatesApplication trait and update to the following:
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->loadEnvironmentFrom('.env.testing');
$app->make(Kernel::class)->bootstrap();
return $app;
}
By using the loadEnvironemtFrom() method, all tests that use this trait will load the .env.testing file and use the connections defined there.
Like Arturo Rojas wrote in his answer you have to check in the constructor, if the connection variable has to be overwritten:
public function __construct(array $attributes = [])
{
if(App::environment() == 'testing') {
$this->connection = env('DB_CONNECTION');
}
parent::__construct($attributes);
}
In your phpunit.xml you need these variables (DB_DATABASE is optional):
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value="testDatabase"/>
<php>
Laravel will then use the sqllite connection from /config/database.php