cant fill properties at __construct in laravel test unit - php

I have created a simple test like this :
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use Illuminate\Support\Facades\Config;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class simpleTest extends TestCase
{
public $form_array = ['forms_data'];
/**
* A basic feature test example.
*
* #return void
*/
public function test_example()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
The test above runs without a problem .
I have a public property $form_array which in the example above has a static value, but when I'm trying to fill $form_array with __construct() in test, I get this error :
array_merge(): Argument #1 must be of type array, null given
at vendor/phpunit/phpunit/phpunit:98
94▕ unset($options);
95▕
96▕ require PHPUNIT_COMPOSER_INSTALL;
97▕
➜ 98▕ PHPUnit\TextUI\Command::main();
99▕
code :
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use Illuminate\Support\Facades\Config;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class simpleTest extends TestCase
{
public $form_array;
public function __construct(){
$this->form_array = ['forms_data'];
}
/**
* A basic feature test example.
*
* #return void
*/
public function test_example()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
My goal is fill the $form_data with values from config files in laravel which i found this way :
public function __construct(){
parent::setUp();
$this->form_array = Config::get('ravandro.form_array');
}
but even a simple $this->form_data = 'value' doesnt work , and im really confused !
btw , Im using Laravel Octane to serve my application but I dont think it has any effect to tests.
Php version : 8.1
Laravel : 9
php-unit: "phpunit/phpunit": "^9.3.3"

In tests the correct constructor method is called setUp(), which runs before every test on that class.
You can check an example of this on https://phpunit.readthedocs.io/en/9.5/fixtures.html#fixtures

Related

Protected Properties PHP Laravel

How can access properties of a protected object PHP while writing tests. Below is my sample code.
TestCase.php The test fails to run saying that the property is protected. But it can die dumped.
<?php
namespace Tests;
use Illuminate\Http\Response;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
public function getAuthUser()
{
$payload = [
'email' => 'admin#gamil.nl',
'password' => 'password',
];
return $this->json('post', 'api/v1/auth/login', $payload)
->assertStatus(Response::HTTP_OK);
}
}
my sample test
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Http\Response;
class PatientsControllerTest extends TestCase
{
/**
* A basic unit test patient controller.
*
* #return void
*/
public function testPatientFetch()
{
$auth_user = $this->getAuthUser();
dd($auth_user->data);
}
}
my sample code
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Http\Response;
class PatientsControllerTest extends TestCase
{
/**
* A basic unit test patient controller.
*
* #return void
*/
public function testPatientFetch()
{
$auth_user = $this->getAuthUser();
dd($auth_user->data);
}
}
Error received
FAIL Tests\Unit\PatientsControllerTest
⨯ patient fetch
---
• Tests\Unit\PatientsControllerTest > patient fetch
PHPUnit\Framework\ExceptionWrapper
Cannot access protected property Illuminate\Http\JsonResponse::$data
at vendor/phpunit/phpunit/phpunit:98
94▕ unset($options);
95▕
96▕ require PHPUNIT_COMPOSER_INSTALL;
97▕
➜ 98▕ PHPUnit\TextUI\Command::main();
99▕
Tests: 1 failed
Time: 1.05s
You can access and manipulate protected variables with ReflectionClass.
Please try this:
$user = $this->getAuthUser();
$reflectionClass = new ReflectionClass($user);
//If we assume we have a protected variable $data we can get access it with Reflection Class
$property = $reflectionClass->getProperty('data');
$property->setAccessible(true); //Here we are making protected variables accessible
$property->setValue($user, 'New Protected Data');
create getter function for $data
public function get_data(){
return $this->data;
}
unit test to test functions and not properties

laravel BadMethodCall() error while caliing for index in api

the Ptag controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Validator;
use App\Ptag;
use App\Http\Resources\PtagResource;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Validation\Rule;
class PtagController extends Controller
{
public function index(Request $request){
$query=Ptag::query();
return PtagResource::collection($query);
}
}
ptag resource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PtagResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id'=>$this->id,
'title'=>$this->title
// 'product'=>$this->product()
];
}
}
ptag model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ptag extends Model
{
protected $guarded=[];
public function product(){
return $this->belongsToMany('App\Product');
}
}
error is shown as such
ErrorException: array_key_exists(): Using array_key_exists() on objects is deprecated. Use isset() or property_exists() instead in file /home/rajesh/project/handicom/vendor/laravel/framework/src/Illuminate/Http/Resources/DelegatesToResource.php on line 53
Problem:
In DelegatesToResource.php file of vendor folder, we have the following code:
public function offsetExists($offset)
{
return array_key_exists($offset, $this->resource);
}
https://github.com/laravel/framework/blob/5.7/src/Illuminate/Http/Resources/DelegatesToResource.php
array_key_exists function is deprecated on PHP 7.4.x. So, the error you met is related to PHP, not Laravel.
Solution:
Method 1: The easiest way is to downgrade your PHP version. Naturally, you still have to ensure the requirements of Laravel 5.7 (PHP >=7.1.3)
Method 2: You upgrade your project to higher version. To limit too many changes in your project, you could use version 5.8. In this version, the library no longer uses array_key_exists function.
public function offsetExists($offset)
{
return isset($this->resource[$offset]);
}
https://github.com/laravel/framework/blob/5.8/src/Illuminate/Http/Resources/DelegatesToResource.php

How to seed database migrations for laravel tests?

Laravel's documentation recommends using the DatabaseMigrations trait for migrating and rolling back the database between tests.
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
$response = $this->get('/');
// ...
}
}
However, I've got some seed data that I would like to use with my tests. If I run:
php artisan migrate --seed
then it works for the first test, but it fails subsequent tests. This is because the trait rolls back the migration, and when it runs the migration again, it doesn't seed the database. How can I run the database seeds with the migration?
All you need to do is make an artisan call db:seed in the setUp function
<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
// seed the database
$this->artisan('db:seed');
// alternatively you can call
// $this->seed();
}
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
$response = $this->get('/');
// ...
}
}
ref: https://laravel.com/docs/5.6/testing#creating-and-running-tests
With Laravel 8, if you're using the RefreshDatabase trait you can invoke seeding from your test case using below:
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
// Run the DatabaseSeeder...
$this->seed();
// Run a specific seeder...
$this->seed(OrderStatusSeeder::class);
$response = $this->get('/');
// ...
}
}
see docs for more information/examples:
https://laravel.com/docs/8.x/database-testing#running-seeders
It took me some digging to figure this out, so I thought I'd share.
If you look at the source code for the DatabaseMigrations trait, then you'll see it has one function runDatabaseMigrations that's invoked by setUp which runs before every test and registers a callback to be run on teardown.
You can sort of "extend" the trait by aliasing that function, re-declare a new function with your logic in it (artisan db:seed) under the original name, and call the alias inside it.
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends TestCase
{
use DatabaseMigrations {
runDatabaseMigrations as baseRunDatabaseMigrations;
}
/**
* Define hooks to migrate the database before and after each test.
*
* #return void
*/
public function runDatabaseMigrations()
{
$this->baseRunDatabaseMigrations();
$this->artisan('db:seed');
}
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
$response = $this->get('/');
// ...
}
}
I know this question has already been answered several times, but I didn't see this particular answer so I thought I'd throw it in.
For a while in laravel (at least since v5.5), there's been a method in the TestCase class specifically used for calling a database seeder:
https://laravel.com/api/5.7/Illuminate/Foundation/Testing/TestCase.html#method_seed
with this method, you just need to call $this->seed('MySeederName'); to fire the seeder.
So if you want this seeder to fire before every test, you can add the following setUp function to your test class:
public function setUp()
{
parent::setUp();
$this->seed('MySeederName');
}
The end result is the same as:
$this->artisan('db:seed',['--class' => 'MySeederName'])
or
Artisan::call('db:seed', ['--class' => 'MySeederName'])
But the syntax is a bit cleaner (in my opinion).
With Laravel 8, the RefreshDatabase is now looking for a boolean property called "seed".
/**
* Illuminate\Foundation\Testing\RefreshDatabase
* Determine if the seed task should be run when refreshing the database.
*
* #return bool
*/
protected function shouldSeed()
{
return property_exists($this, 'seed') ? $this->seed : false;
}
Simply give your test class the protected property $seed and set it to true if you wish to seed.
class ProjectControllerTest extends TestCase
{
protected $seed = true;
public function testCreateProject()
{
$project = Project::InRandomOrder()->first();
$this->assertInstanceOf($project,Project::class);
}
The nice part about this method is that individual tests won't seed everytime they are ran. Only seed necessary test will build the database.
If you're using the RefreshDatabase testing trait:
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, RefreshDatabase {
refreshDatabase as baseRefreshDatabase;
}
public function refreshDatabase()
{
$this->baseRefreshDatabase();
// Seed the database on every database refresh.
$this->artisan('db:seed');
}
}
Here is an alternate solution, in case you prefer to bypass Artisan's native DatabaseMigrations and seeder/migration methods. You can create your own trait to seed your database:
namespace App\Traits;
use App\Models\User;
use App\Models\UserType;
trait DatabaseSetup
{
public function seedDatabase()
{
$user = $this->createUser();
}
public function createUser()
{
return factory(User::class)->create([
'user_type_id' => function () {
return factory(UserType::class)->create()->id;
}
]);
}
public function getVar() {
return 'My Data';
}
}
Then call it in your test like this:
use App\Traits\DatabaseSetup;
class MyAwesomeTest extends TestCase
{
use DatabaseSetup;
use DatabaseTransactions;
protected $reusableVar;
public function setUp()
{
parent::setUp();
$this->seedDatabase();
$this->reusableVar = $this->getVar();
}
/**
* #test
*/
public function test_if_it_is_working()
{
$anotherUser = $this->createUser();
$response = $this->get('/');
$this->seeStatusCode(200);
}
}

Laravel Service provider not working

I've bind my interface called CustomerRepository to EloquentCustomerRepository. This is my CustomerServiceProvider:
public function register()
{
$this->app->bind(CustomerRepository::class,EloquentCustomerRepository::class);
$this->app->bind(PackageRepository::class,EloquentPackageRepository::class);
}
When I try to instantiate it in my controller like this:
<?php
namespace App\Http\Controllers\api\v1;
use Lsupport\repositories\api\v1\customer\CustomerRepository;
use App\Http\Controllers\Controller;
use Lsupport\customer\Customer;
use App\Http\Requests;
class CustomerController extends Controller
{
protected $CustomerRepository;
public function __construct(CustomerRepository $CustomerRepository)
{
$this->CustomerRepository = $CustomerRepository;
}
It throws the following error:
Target [Lsupport\repositories\api\v1\Customer\CustomerRepository] is not instantiable while building [App\Http\Controllers\api\v1\CustomerController].
I also registered it in app.config:
App\Providers\CustomerServiceProvider::class,
What am I doing wrong?
CustomerServiceProvider
<?php
namespace App\Providers;
use Lsupport\repositories\api\v1\customer\EloquentCustomerRepository;
use Lsupport\repositories\api\v1\customer\EloquentPackageRepository;
use Lsupport\repositories\api\v1\customer\CustomerRepository;
use Lsupport\repositories\api\v1\customer\PackageRepository;
use Illuminate\Support\ServiceProvider;
class CustomerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind(CustomerRepository::class,EloquentCustomerRepository::class);
$this->app->bind(PackageRepository::class,EloquentPackageRepository::class);
}
}
CustomerRepository
<?php
namespace Lsupport\repositories\api\v1\Customer;
interface CustomerRepository
{
public function create($request);
}
**EloquentCustomerRepository**
<?php
namespace Lsupport\repositories\api\v1\customer;
use Lsupport\repositories\api\v1\customer\CusteromRepositoryTrait;
use Lsupport\repositories\api\v1\remain\RightTrait;
use Lsupport\repositories\api\v1\remain\JsonTrait;
use Lsupport\customer\Customer;
class EloquentCustomerRepository implements CustomerRepository
{
use JsonTrait;
use RightTrait;
use CustomerRepositoryTrait;
code.....
Ok, the first thing I notice is that you probably want the same namespaces on the interface and on the class. So, the namespace of EloquentCustomerRepository should be
namespace Lsupport\repositories\api\v1\Customer;
and not
namespace Lsupport\repositories\api\v1\customer;
(with lower customer).
Now, on your CustomerServiceProvider, you should use:
public function register()
{
$this->app->bind('Lsupport\repositories\api\v1\Customer\CustomerRepository', 'Lsupport\repositories\api\v1\Customer\EloquentCustomerRepository');
}
Make sure you run composer dumpautoload -o on the command line.

Adding subdirectory to tests in Laravel; connection can't be resolved

I'm trying to add some structure into my /tests/ directory, at the moment files are structured like this:
/tests
/Models
UserTest.php
ExampleTest.php
TestCase.php
Both UserTest and ExampleTest are extended from TestCase. Running Exampletest works just fine (adds user to database), whereas UserTest fails with the following error:
Fatal error: Call to a member function connection() on null in [...]\Illuminate\Datab ase\Eloquent\Model.php on line 3340
That means that this functions receives null or no argument (model.php:3338-3342):
public static function resolveConnection($connection = null)
{
return static::$resolver->connection($connection);
}
I cannot figure out what's different in my two testcases.
TestCase.php:
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as baseTestCase;
class TestCase extends baseTestCase
{
/**
* The base URL to use while testing the application.
*
* #var string
*/
protected $baseUrl = 'http://localhost';
/**
* Creates the application.
*
* #return \Illuminate\Foundation\Application
*/
public function createApplication()
{
$app = require __DIR__ . '/../bootstrap/app.php';
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
return $app;
}
}
ExampleTest.php:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use App\User;
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
User::create([
"name" => 'test'
]);
$this->visit('/')
->see('Laravel 5');
}
}
Models/UserTest.php:
<?php
namespace Tests\Models;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
use Faker\Factory;
use App\User;
class UserTest extends TestCase
{
use DatabaseTransactions;
protected $user;
protected $password;
protected $customPassword;
protected $testString;
/**
* Construct.
*
* #return void
*/
public function __construct()
{
$faker = Factory::create();
$this->password = $faker->password;
$this->customPassword = $faker->password;
$this->user = User::create([
"name" => $faker->name,
"email" => $faker->email,
"password" => Hash::shouldReceive($this->password),
]);
}
public function testExample()
{
$this->assertTrue(true);
}
}
Anyone has an idea what's going on here?
I guess this is happening because as you are extending "TestCase" from Models/UserTest and it is looking "TestCase" inside your Models folder, try giving a full path to that class.
Something like:
class UserTest extends {yourNameSpace(ifAny)}\TestCase

Categories