I have a code that runs everyday and deletes some information from the database.
I am trying to test this code using artisan's test functionality and would like to be able to see the final result on phpmyadmin, however if I add Illuminate\Foundation\Testing\RefreshDatabase The DB seems to refresh at the start AND at the end.
Is there a way to refresh the database at the start only?
Here is a shortened sample of my code:
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Server\Models\User;
use Server\Models\...; //call multiple models
use Tests\TestCase;
class TestRemoveCertainData extends TestCase
{
use RefreshDatabase;
public function removeCertainData()
{
//create all necessary data using factory
factory(User::class)->create(); // etc...
//should run the code that deletes certain data
$this->artisan('remove_data_command')->assertSuccessful();
}
}
So after I run php artisan test Tests\Feature\TestRemoveCertainData I would like to check if php artisan remove_data_command worked the way I intended it to on the phpmyadmin panel.
I wasn't able to find a method for it.
But what I understood was that the idea of keeping the database, and checking it yourself was wrong.
The Laravel Tests themselves are supposed to be something that someone can run in the future with one simple command, such as php artisan test --group=my_group_name.
Therefore the correct way to solve this is to add an assert and compare the tables to your expectations.
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Server\Models\User;
use Server\Models\...; //call multiple models
use Tests\TestCase;
class TestRemoveCertainData extends TestCase
{
use RefreshDatabase;
public function testRemoveCertainData()
{
$users = [
$this->createUserType1(),
$this->createUserType2(),
$this->createUserType3(),
$this->createUserType4(),
];
// deletes emails for users of type 2 and 4
$this->artisan('delete_certain_emails')->assertSuccessful();
// expecting the emails of user 2 and 4 to disappear
$expected = [
$users[0]->mail,
null,
$users[2]->mail,
null,
];
$result = User::pluck('mail')->toArray();
$this->assertEquals($expected, $result);
}
}
Related
I'm trying to access a Model in Laravel app through its Class Name, but I'm getting the following error:
Class 'Asset' not found
Though, I've already imported this Model.
Here's my code:
<?php
namespace App\Services;
use App\Models\Asset as Asset;
use App\Models\Benefit\BenefitGroup as BenefitGroup;
use App\Models\Benefit\EmployeeDependent as EmployeeDependent;
use App\Models\Country as Country;
use App\Models\Employee\Education as Education;
use App\Models\Employee\EducationType as EducationType;
use App\Models\Employee\Employee as Employee;
use App\Models\Employee\EmployeeVisa as EmployeeVisa;
use App\Models\Employee\VisaType as VisaType;
use App\Models\Note as Note;
use App\Models\PaidTimeOff\Policy as Policy;
use App\Models\PaidTimeOff\TimeOffType as TimeOffType;
use Illuminate\Database\Eloquent\Model;
class ACLService
{
public function getDefaultPermissions($roleType)
{
//Array to append all default permissions in single array without extra indexing
$permissions=[];
$models=['Asset', 'Employee', 'Education', 'EducationType', 'VisaType', 'EmployeeVisa', 'BenefitGroup', 'EmployeeDependent',
'Policy', 'TimeOffType','Country', 'Note'];
foreach ($models as $model) {
$permissions=$this->getModelPermissions($model, $roleType, $permissions);
}
}
public function getModelPermissions($model, $roleType, $currentPermissions)
{
$data=$model::getDefaultPermissionsForThisModel($roleType, $currentPermissions);
return $data;
}
}
If I pass
App\Models\Asset
instead of
Asset
then the error gets resolved but I don't want to pass data in this format because it'll increase the work amount if we ever get to change (in future) the Model's location in the project.
This is an incomplete project and we'll definitely change the models' location.
I'm looking for a cleaner solution.
Use the ::class constant to get the correctly namespaced class name. This will also make it easier if you ever move the files. Smart IDE's will be able to detect the reference.
$models=[
Asset::class,
Employee::class,
...
];
This presumes all classes have been imported at the top of your PHP file.
As a tester, I want to feel more secure by having each test start with a fresh database (one starting entry).
With Laravel and PHPUnit, you can do this with the RefreshDatabase trait. This stopped working for my latest test.
I then tried MyModel::destroy($id). This also didn't destroy the new entries.
How do I destroy the brain of these zombie entries?
I have:
a table called "users" with one user
a model called User
a test for activating deactivating users
use of the RefreshDatabase trait
PHPUnit using SQLite in :memory:
Laravel 6.5.2
Here's my test; I've commented about the problem.
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
use Tests\TestCase;
class UsersTest extends TestCase
{
use RefreshDatabase;
/**
* Activate user test
*
* #test
* #return void
*/
public function asAdminActivateUser()
{
// (Test code)
$user = factory(User::class)->create();
// Prove the newly gen'd user has the id: 2.
assert(User::find(2)->name === $user->name, "The factory gen an entry with a diff id");
// (More test code)
// In a following test, the db still had two users (should be one), so I used this
User::destroy(2);
// This fails
assert(User::all()->count() === 1, "The destroy method didn't work!");
}
}
I've tried:
use RefreshDatabase;
User::find(2)->delete();
use DatabaseMigrations;
some long script that works directly with DB;
User::destroy([2]);
What's the cause of User::destroy(2) not destroying something? What can I do to make it destroy the entry?
First, there's a use DatabaseTransactions; Trait that you may want to look at because I believe that there have been documented performance reasons for why RefreshDatabase does not reset the auto-increment value after each test.
As for your questions, as you mentioned this would be the correct syntax User::find(2)->delete(), if you were certain that you were actually working with a user with an id of 2, however, because of what I mentioned earlier the tables auto-incrementing id column is likely past 2 because of other tests you have already run up to that point.
As your proof is hypothetical and likely does not reflect the actual implementation that you are actually facing in the real code, what can choose to remove the user yourself after you have run your assertions against the user:
...
// (Test code)
$user = factory(User::class)->create();
// Prove the newly gen'd user has the id: 2.
assert(User::find(2)->name === $user->name, "The factory gen an entry with a diff id");
$user->delete();
...
An alternative would be to clean up your database manually with User::orderByDesc('id')->take(1)->delete() or User::skip(1)->delete() to return your database to having one User.
I'm asking/answering because I have had so much trouble getting this working and I'd like to show a step-by-step implementation.
References:
https://laravel.com/docs/5.0/facades#creating-facades
http://www.n0impossible.com/article/how-to-create-facade-on-laravel-51
This may not be the only way to implement facades in Laravel 5, but here is how I did it.
We're going to create a custom Foo facade available in the Foobar namespace.
1. Create a custom class
First, for this example, I will be creating a new folder in my project. It will get its own namespace that will make it easier to find.
In my case the directory is called Foobar:
In here, we'll create a new PHP file with our class definition. In my case, I called it Foo.php.
<?php
// %LARAVEL_ROOT%/Foobar/Foo.php
namespace Foobar;
class Foo
{
public function Bar()
{
return 'got it!';
}
}
2. Create a facade class
In our fancy new folder, we can add a new PHP file for our facade. I'm going to call it FooFacade.php, and I'm putting it in a different namespace called Foobar\Facades. Keep in mind that the namespace in this case does not reflect the folder structure!
<?php
// %LARAVEL_ROO%/Foobar/FooFacade.php
namespace Foobar\Facades;
use Illuminate\Support\Facades\Facade;
class Foo extends Facade
{
protected static function getFacadeAccessor()
{
return 'foo'; // Keep this in mind
}
}
Bear in mind what you return in getFacadeAccessor as you will need that in a moment.
Also note that you are extending the existing Facade class here.
3. Create a new provider using php artisan
So now we need ourselves a fancy new provider. Thankfully we have the awesome artisan tool. In my case, I'm gonna call it FooProvider.
php artisan make:provider FooProvider
Bam! We've got a provider. Read more about service providers here. For now just know that it has two functions (boot and register) and we will add some code to register. We're going to bind our new provider our app:
$this->app->bind('foo', function () {
return new Foo; //Add the proper namespace at the top
});
So this bind('foo' portion is actually going to match up with what you put in your FooFacade.php code. Where I said return 'foo'; before, I want this bind to match that. (If I'd have said return 'wtv'; I'd say bind('wtv', here.)
Furthermore, we need to tell Laravel where to find Foo!
So at the top we add the namespace
use \Foobar\Foo;
Check out the whole file now:
<?php
// %LARAVEL_ROOT%/app/Providers/FooProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Foobar\Foo;
class FooProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind('foo', function () {
return new Foo;
});
}
}
Make sure you use Foobar\Foo and not Foobar\Facades\Foo - your IDE might suggest the wrong completion.
4. Add our references to config/app.php
Now we have to tell Laravel we're interested in using these random files we just created, and we can do that in our config/app.php file.
Add your provider class reference to 'providers': App\Providers\FooProvider::class
Add your facade class reference to 'aliases': 'Foo' => Foobar\Facades\Foo::class
Remember, in aliases, where I wrote 'Foo', you will want to put the name you want to reference your facade with there. So if you want to use MyBigOlFacade::helloWorld() around your app, you'd start that line with 'MyBigOlFacade' => MyApp\WhereEverMyFacadesAre\MyBigOlFacade::class
5. Update your composer.json
The last code change you should need is to update your composer.json's psr-4 spaces. You will have to add this:
"psr-4": {
"Foobar\\" : "Foobar/",
// Whatever you had already can stay
}
Final move
Okay so now that you have all that changed, the last thing you need is to refresh the caches in both composer and artisan. Try this:
composer dumpautoload
php artisan cache:clear
Usage & A Quick Test:
Create a route in app/routes.php:
Route::get('/foobar', 'FooBarController#testFoo');
Then run
php artisan make:controller FooBarController
And add some code so it now looks like this:
<?php
namespace App\Http\Controllers;
use Foobar\Facades\Foo;
use App\Http\Requests;
class FooBarController extends Controller
{
public function testFoo()
{
dd(Foo::Bar());
}
}
You should end up with the following string:
Troubleshooting
If you end up with and error saying it cannot find the class Foobar\Facades\Foo, try running php artisan optimize
sorry if it is a noob question, but I'm trying to learn laravel on laracast and can't solve this by my own.
there is a store function on my ArticlesController like this:
public function store(ArticleRequest $request)
{
$article = new Article($request->all());
Auth::user()->articles()->save($article);
return redirect('articles');
}
and it returns a blank page, making clear that is some error, but if I change to
\Auth::user()->articles()->save($article);
it works as expected, saving the article with the user_id field.
I tried import with use App\Http\Controllers\Auth\AuthController; but I think this is not the way.
*obs: Laravel 5.0
In modern PHP, if you see the following at the top of a file
namespace App\Foo\Bar;
it means all the code inside that file in part of the App\Foo\Bar namespace. If you try to use a namespaceless class inside this file
$object = new Auth;
PHP will assume you want to use the class. In other words, it's the same as saying
$object = \App\Foo\Bar\Auth;
When you say
\Auth
you're telling PHP "use the global, top level namespace class named Auth.
This is a perfectly valid use of PHP, by the way. However, if you don't like using the global \ -- you can import the \Auth class into your PHP file (sometimes referred to as a "module" in other languages") by using the use statement.
namespace App\Foo\Bar;
//pull in the global class `Auth`
use Auth;
//...
$foo = new Auth;
I'm in the process of switching from Laravel 4.2 to Laravel 5, not sure if that is relevant, but I am getting an error:
"Class 'library\observers\UserObserver' not found"
and I have no Idea what the problem is, as far as I can see ( through my frustration mind you ) is that everything is in its right place, name spaces, folders, class names etc.. and I've ran the artisan dump autoload command twice now. the class is an observer which modifies user input on save. here is my code:
UserObserverServiceProvider.php:
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use library\observers\UserObserver;
use App\Models\User;
class UserObserverServiceProvider extends ServiceProvider
{
public function boot()
{
User::observe( new UserObserver );
}
public function register(){}
}
UserObserver.php:
<?php namespace library\observers;
use library\Facades\Geo;
use Geocode;
use State;
use City;
class UserObserver{ code for user observer }
app.php configuration for service provider:
'App\providers\UserObserverServiceProvider',
All of these things were working together before the switch, what am I missing?
I left the App out of the namespace and path for use, works now, thanks!