Laravel - setting up a test database - laravel

With my laravel application I have a bunch of migrations and db seed data.
In app/config/testing/database.php I have set a mysql database for testing.
I've populated the test database with migrate and db:seed commands, specifying the testing environment.
This works pretty well, but I want to call those commands each time I run phpunit. If it do it in the setUp then it takes so long as it's called on every single test.
I can also call it in the setUpBeforeClass which is better, but still longer if I am testing 10 classes.
Is there a way I can all it just once when I run phpunit ?

For now I have done this, in the tests/TestCase.php
class TestCase extends Illuminate\Foundation\Testing\TestCase {
public static $setupDatabase = true;
public function setUp()
{
parent::setUp();
if(self::$setupDatabase)
{
$this->setupDatabase();
}
}
public function setupDatabase()
{
Artisan::call('migrate');
Artisan::call('db:seed');
self::$setupDatabase = false;
}
....
which seems to work, but doesn't feel ideal, but means I can easily re-setup the db if I need to reset it for a test...

Related

Laravel unit testing automatic dependency injection not working?

With Laravel Framework 5.8.36 I'm trying to run a test that calls a controller where the __construct method uses DI, like this:
class SomeController extends Controller {
public function __construct(XYZRepository $xyz_repository)
{
$this->xyz_repository = $xyz_repository;
}
public function doThisOtherThing(Request $request, $id)
{
try {
return response()->json($this->xyz_repository->doTheRepoThing($id), 200);
} catch (Exception $exception) {
return response($exception->getMessage(), 500);
}
}
}
This works fine if I run the code through the browser or call it like an api call in postman, but when I call the doThisOtherThing method from my test I get the following error:
ArgumentCountError: Too few arguments to function App\Http\Controllers\SomeController::__construct(), 0 passed in /var/www/tests/Unit/Controllers/SomeControllerTest.php on line 28 and exactly 1 expected
This is telling me that DI isn't working for some reason when I run tests. Any ideas? Here's my test:
public function testXYZShouldDoTheThing()
{
$some_controller = new SomeController();
$some_controller->doThisOtherThing(...args...);
...asserts...
}
I've tried things like using the bind and make methods on app in the setUp method but no success:
public function setUp(): void
{
parent::setUp();
$this->app->make('App\Repositories\XYZRepository');
}
That's correct. The whole idea of a unit test is that you mock the dependant services so you can control their in/output consistently.
You can create a mock version of your XYZRepository and inject it into your controller.
$xyzRepositoryMock = $this->createMock(XYZRepository::class);
$some_controller = new SomeController($xyzRepositoryMock);
$some_controller->doThisOtherThing(...args...);
This is not how Laravels service container works, when using the new keyword it never gets resolved through the container so Laravel cannot inject the required classes, you would have to pass them yourself in order to make it work like this.
What you can do is let the controller be resolved through the service container:
public function testXYZShouldDoTheThing()
{
$controller = $this->app->make(SomeController::class);
// Or use the global resolve helper
$controller = resolve(SomeController::class);
$some_controller->doThisOtherThing(...args...);
...asserts...
}
From the docs :
You may use the make method to resolve a class instance out of the
container. The make method accepts the name of the class or interface
you wish to resolve:
$api = $this->app->make('HelpSpot\API');
If you are in a location of your code that does not have access to the
$app variable, you may use the global resolve helper:
$api = resolve('HelpSpot\API');
PS:
I am not really a fan of testing controllers like you are trying to do here, I would rather create a feature test and test the route and verify everything works as expected.
Feature tests may test a larger portion of your code, including how
several objects interact with each other or even a full HTTP request
to a JSON endpoint.
something like this:
use Illuminate\Http\Response;
public function testXYZShouldDoTheThing()
{
$this->get('your/route')
->assertStatus(Response::HTTP_OK);
// assert response content is correct (assertJson etc.)
}

Laravel unit testing assert functions always failing

I have created a very basic unit testing class under Laravel root-->tests-->Unit-->SimpleTest.php
Inside the file i have import the controller class that i need to test. And test function is like this.
class SimpleTest extends TestCase
{
public function testLoadUsers()
{
$controller_obj = new UsersController;
$data_status = $controller_obj->load_users();
if($data_status != null){
$this->assertTrue(true);
}
else{
$this->assertTrue(false);
}
}
}
I executed this test case in Artisan console like this,
php vendor/phpunit/phpunit/phpunit tests/Unit/SimpleTest.php
Always this fails. My controller function returning data as well without an issue. I tested without defining any condition and just,
$this->assertTrue(true);
Then it works. So i assume there is no any issue with the phpunit test command as well.
I'm sorry, this is not a direct answer.
I think maybe you should check the value of $data_status by this.
class SimpleTest extends TestCase
{
public function testLoadUsers()
{
$controller_obj = new UsersController;
$data_status = $controller_obj->load_users();
$this->expectOutputString('Array');
print data_status;
}
}

Laravel Undefined property in job

In a Laravel job I have:
use Spatie\Valuestore\Valuestore;
and
public function __construct()
{
$this->settings = Valuestore::make(storage_path('app/settings.json'));
}
and
public function handle()
{
if($this->settings->get('foo') == 'test') {
etc...
and on this I get an error Undefined property App\Jobs\MyJobName::$settings. What is going wrong?
Even if I do this:
public function handle()
{
$this->settings = Valuestore::make(storage_path('app/settings.json'));
if($this->settings->get('foo') == 'test') {
etc...
I get the same error.
Update based on the comments
MyJobName is called in a custom artisan command, that happens to also use Valuestore but I assume that would unrelated.
In the class CustomCommand:
use Spatie\Valuestore\Valuestore;
and
public function __construct()
{
parent::__construct();
$this->settings = Valuestore::make(storage_path('app/settings.json'));
}
and
public function handle()
{
if($this->settings->get('foo') == 'test') // This works in this custom command!
{
$controller = new MyController;
MyJobName::dispatch($controller);
}
}
So in CustomCommand I use Valuestore in exactly the same way as in MyJobName but in the latter it doesn't work.
As per one of the comments: I do not make $this->settings global as I don't do that in CustomCommand either and it works fine there.
Update 2
If I add protected $settings; above the __construct() function as per the comments it still doesn't work, same error.
Just declare the settings property as public in your Job Class.
public $settings;
public function __construct()
{
$this->settings = Valuestore::make(storage_path('app/settings.json'));
}
I recently had this error. I've tried to make the variables public, delete all the variable inside the Jobs class and even rename and delete the class itself. But it didn't work.
Shortly, I run this artisan command php artisan optimize:clear to clear all the caches, views, routes, etc. And it somehow solve the problem about variable in my problem. For anyone who is still have this OP's problem, give a try to my solution above.
If you use JOB by QUEUE, you need all the requests or SQL queries to do by the method handle
public function handle()
{
$this->settings = Valuestore::make(storage_path('app/settings.json'));
....
}
Because the constructor works when you make the object of class, and this object is serialized and stored in the database and after the unserialization and the handle is triggered.
You may need to restart your queue worker
From Laravel documentation
Remember, queue workers are long-lived processes and store the booted application state in memory. As a result, they will not notice changes in your code base after they have been started. So, during your deployment process, be sure to restart your queue workers.
If you use a daemon php artisan queue:restart
If you use queue:work on your bash hit Ctrl+C then again php artisan queue:work should be enough

Reset Redis database after each test in laravel

In my laravel application, I use Redis to store some cache (e.g. the list of items to show on the front page). I always access Redis through the Facade: Illuminate\Support\Facades\Redis.
I created a different Redis database for testing (1 instead of 0), but I also need to reset it after each test, so that the test never gets data from a previous test.
Is there an efficient way to create this behaviour?
I tried to implement it using the #before annotation:
/**
* #before
*/
public function prepareForTesting() {
Redis::flushdb();
}
But I get the error: Cannot use 'FLUSHDB' over clusters of connections.
Any ideas?
Maybe you could use the built in artisan cache:clear command?
Like this:
/**
* #before
*/
public function prepareForTesting() {
Artisan::call('cache:clear');
}

Can you exclude one test method in setup? (Laravel Testing Phpunit)

I'm looking for an elegant way to exclude one test method of the phpunit setup.
To explain it further you see this code:
public function setUp()
{
parent::setUp();
$this->signUp; //creates and logs in the user
}
/** #test */
public function guest_cannot_see_request_page()
{
$this->get(route('requests.list'))
->assertRedirect(route('login'));
}
But I want to exclude the signIn for the guest_cannot_see_request_page() method. Since it should be a guest. For all my other methods the user is logged in.
You can achieve the desired result if you rename your setUp() method to manualSetUp() and call it at the beginning of the test methods you that code to run on.

Resources