I tried creating a unit test for the relationships between my User and Shop models, however when I run vendor\\bin\\phpunit this error(s) are thrown, I have no idea about this since I'm a newbie in unit testing. I tried to run my code on my controller to see if the relationship actually works, and fortunately it is working as expected, but not when run in phpunit. What have I done wrong for this phpunit not to work with Models?
Fatal error: Uncaught Error: Call to a member function connection() on null in E:\projects\try\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:1013
Stack trace:
E:\projects\try\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php(979): Illuminate\Database\Eloquent\Model::resolveConnection(NULL)
This is my UserTest.php
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\User;
use App\Shop;
class UserTest extends TestCase
{
protected $user, $shop;
function __construct()
{
$this->setUp();
}
function setUp()
{
$user = new User([
'id' => 1,
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'JohnDoe#example.com',
'password' => 'secret',
'facebook_id' => null,
'type' => 'customer',
'confirmation_code' => null,
'newsletter_subscription' => 0,
'last_online' => null,
'telephone' => null,
'mobile' => null,
'social_security_id' => null,
'address_1' => null,
'address_2' => null,
'city' => null,
'zip_code' => null,
'signed_agreement' => 0,
'is_email_confirmed' => 0
]);
$user = User::find(1);
$shop = new Shop([
'id' => 1,
'user_id' => $user->id,
'name' => 'PureFoods Hotdog2',
'description' => 'Something that describes this shop',
'url' => null,
'currency' => 'USD'
]);
$user->shops()->save($shop);
$shop = new Shop([
'id' => 2,
'user_id' => $user->id,
'name' => 'PureFoods Hotdog',
'description' => 'Something that describes this shop',
'url' => null,
'currency' => 'USD'
]);
$user->shops()->save($shop);
$this->user = $user;
}
/** #test */
public function a_user_has_an_id(){
$user = User::find(1);
$this->assertEquals(1, $user->id);
}
/** #test */
public function a_user_has_a_first_name()
{
$this->assertEquals("John", $this->user->first_name);
}
/** #test */
public function a_user_can_own_multiple_shops()
{
$shops = User::find(1)->shops()->get();
var_dump($this->shops);
$this->assertCount(2, $shops);
}
}
It seems, this error is caused by this line of code:
$user->shops()->save($shop); - this code actually works when run in my sample routes or Controller but is throwing errors when run in phpunit
User.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
protected $guarded = [ 'id' ];
protected $table = "users";
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* returns the Shops that this user owned
*/
public function shops()
{
return $this->hasMany('App\Shop');
}
}
Shop.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Shop extends Model
{
protected $guarded = [ 'id' ];
protected $table = "shops";
/**
* returns the Owner of this Shop
*/
public function owner()
{
return $this->belongsTo('App\User');
}
}
Any help will be very much appreciated. Thanks!
First of all, setUp() is called before every test, so you shouldn't call it from within the constructor
Second of all, you should call the parent::setUp() in your setUp() to initialize the app instance.
One more reason
check if the test class is extending use Tests\TestCase; rather then use PHPUnit\Framework\TestCase;
Laravel ships with the later, but Tests\TestCase class take care of setting up the application, otherwise models wont be able to communicate with the database if they are extending PHPunit\Framework\TestCase.
Example:
<?php
class ExampleTest extends TestCase
{
private $user;
public function setUp()
{
parent::setUp();
$this->user = \App\User::first();
}
public function testExample()
{
$this->assertEquals('victor#castrocontreras.com', $this->user->email);
}
}
a solution
use PHPunit\Framework\TestCase
Put this
use Tests\TestCase
You are assigning the id '2' to both shops.
Assigning is should not be neccessary since I assume the shop table id field is an autoincrement field.
Also look into database factories in the Laravel docs, it will simplify things for you.
That can be happens when you try to read database from dataprovider function. Like me tried, yes. The work way:
protected static $tariffs_default;
protected function setUp(): void {
parent::setUp ();
if (!static::$tariffs_default){
static::$tariffs_default = DB::...;
}
}
// in dataprovider we use string parm, named as our static vars
public static function provider_prov2(){
return [
[".....", [ 'tariffs'=>'tariffs_default'] ],
];
}
// then, in test we can ask our data by code:
public function testSome(string $inn, string $ogrn, $resp_body){
if ( $ta_var = Arr::get( $resp_body, 'tariffs' ) ){
Arr::set($resp_body, 'tariffs', static::$$ta_var );
}
$this->assert...
}
Cause of the problem: you can't use Laravel Models functionality from code called in the constructor of Class UserTest - even though you put the code in the method "setUp", you unnecessarily called it from the constructor. SetUp is called by phpUnit without you needing to do it in the constructor.
When the UserTest constructor has run, the Laravel Bootstrap code has not yet been called.
When the UserTest->setUp() method is called, the Laravel Bootstrap code HAS been run, so you can use you Models etc.
class UserTest extends TestCase
{protected $user, $shop;
function __construct()
{
$this->setUp(); // **THIS IS THE PROBLEM LINE**
}
function setUp()
{
$user = new User([....
Try composer dump-autoload
~Regards
Related
I have created a seeder that populates data in Laravel but was having a BadMethodCallException Call to undefined method App\Models\Project::factory(). Why is this happening? Below is my seeder code.
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\{
Project, User
};
class ProjectSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Project::factory()->count(10)->create();
foreach(Project::all() as $project) {
$users = User::inRandomOrder()->take(rand(1, 3))->pluck('id');
$project->users()->attach($users);
}
}
}
First check the project factory class inside path
database/factories/ProjectFactory.php
If it not exist create it
<?php
namespace Database\Factories;
use App\Models\Project;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProjectFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Project::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
//add your custom seeder data
return [
"project_tittle" => $this->faker->catchPhrase,
"client_name" => $this->faker->name(),
"days" => rand(45, 60),
"description" => $this->faker->text,
"start_date" => $this->faker->date('Y-m-d'),
"end_date" => $this->faker->date('Y-m-d'),
"current_status" => 1,
"completion_percentage" => 0
];
}
}
If your are using different namespace on model you need to add model like this in your factoryclass
protected $model = Project::class;
I hope it works for you
I've solved it temporarily by using DB facade instead of a factory.
use Illuminate\Support\Facades\DB;
DB::table('projects')->insert([
'name' => Str::random(10),
'created_at' => now(),
'updated_at' => now(),
]);
I am getting the following error 'The process has been signaled with signal "11".' when performing a self reference relationship inside my CategoryFactory. For instance i have the following code:
CategoryTest.php
<?php
namespace Tests\Unit\Models\Categories;
use App\Models\Category;
use Tests\TestCase;
class CategoryTest extends TestCase
{
/**
* A basic unit test example.
*
* #return void
*/
public function test_it_has_children()
{
$category = Category::factory()
->has(Category::factory()->count(3), 'children')
->create();
$this->assertInstanceOf(Category::class, $category->children->first());
}
}
Looks like here is where the error is coming from. I am now sure if this is the correct way to reference a hasMany relationship.
CategoryFactory.php
<?php
namespace Database\Factories;
use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class CategoryFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Category::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'name' => $name = $this->faker->unique()->name,
'slug' => Str::of($name)->slug('-'),
'parent_id' => Category::factory() // This seems to be causing the error
];
}
}
Category.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
'order'
];
public function children()
{
return $this->hasMany(Category::class, 'parent_id', 'id');
}
}
I have no clue what this error might be pointing to
About the line, which as you think seems to be causing error.
I think the error could be caused by recursion or something like this.
Try to change it to:
'parent_id' => function () {
return factory(Category::class)->create()->id;
}
Everytime you call Category::factory()->create(), it is internally creating a parent for it by calling Category::factory()->create() so this is an eternal loop.
Your child creation logic is fine, and they are getting the correct parent assigned.
You can solve this by assigning a null parent to the top-level category when using it
$category = Category::factory()
->has(Category::factory()->count(3), 'children')
->create(['parent_id' => null]);
It would be better if you change it in the factory so that the automatically created parent is a top-level category.
public function definition()
{
return [
'name' => $name = $this->faker->unique()->name,
'slug' => Str::of($name)->slug('-'),
'parent_id' => Category::factory(['parent_id' => null]) //Here
];
}
As a side note, your factory would behave in a weird way if you just provide the name argument. As the slug would be for a different name
//Currently
Category::factory()->create();
//Creates ['name' => 'Random Name', 'slug' => 'random-name']
Category::factory()->create(['name' => 'Johny Johnson']);
//Creates ['name' => 'Johny Johnson', 'slug' => 'random-name-not-related-to-name']
//By changing this
public function definition()
{
return [
'name' => $this->faker->unique()->name,
'slug' => fn($data) => Str::of($data['name'])->slug('-'), //Notice the function here
'parent_id' => Category::factory(['parent_id' => null])
];
}
//With the change
Category::factory()->create();
//Creates ['name' => 'Random Name', 'slug' => 'random-name']
Category::factory()->create(['name' => 'Johny Johnson']);
//Creates ['name' => 'Johny Johnson', 'slug' => 'johny-johnson']
I want to pass arguments ['site_id' => $site->id] to SiteMessage factory:
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\SiteMessage;
use App\Models\Site;
class SitesMessagesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Site::chunk(200, function ($sites) {
foreach ($sites as $site) {
SiteMessage::factory()->count(rand(2, 6))->create(['site_id' => $site->id]);
}
});
}
}
How can I get those argument in my SiteMessage factory class?
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\SiteMessage;
use App\Models\Site;
use App\Models\Integration;
class SiteMessageFactory extends Factory
{
protected $model = SiteMessage::class;
public function definition()
{
return [
**// Soliution: remove line below, it will be overridden automaticaly. \\**
'site_id' => $arguments['site_id'], // Neet to use Id that I passed from seeder.
'integration_id'=> Integration::inRandomOrder()->first()->id,
'type' => rand(0,1) ? 'EMAIL' : 'SMS',
'title' => $this->faker->text($maxNbChars = 12),
'description' => $this->faker->sentence,
'message' => $this->faker->sentence,
'enabled' => 1,
'created_at' => now(),
'updated_at' => now(),
];
}
}
At older Laravel factory version I could pass them in callback like so:
$factory->define(SiteMessage::class, function (Faker $faker, array $arguments = []) {
//
});
but don't know how to achieve it with new Class factories. Any help would be very appreciated :)
As you can see in the laravel documentation about persisting models with factories, when you type:
SiteMessage::factory()->count(rand(2, 6))->create(['site_id' => $site->id]);
The site_id attribute from SiteMessage factory will be overrided by the $site->id you are specifying.
I'm trying to set-up PHPunit with Laravel 5.2. I followed the documentation for a simple unit test, however every test throws the same error:
1) CreateAccountTest::testCreateUserWithInvalidEmail
BadMethodCallException: Call to undefined method
Illuminate\Database\Query\Builder::make()
/some/path/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2405
/some/path/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:1426
/some/path/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:3526
/some/path/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:504
/some/path/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:504
/some/path/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:73
/some/path/tests/UnitTests/CreateAccountTest.php:32
My unit tests all look similar to this, except it's asserting a different property every time:
class CreateAccountTest extends TestCase
{
protected $body;
protected $app;
protected function setUp() {
parent::setUp();
$this->app = factory(\App\Models\App::class)->create([
'name' => 'Test Suite'
]);
$this->body = [
'name' => 'Doe',
'firstName' => 'John',
'login' => 'john.doe',
'email' => 'john.doe#johndoe.com',
'password' => 'test1324',
'repeatPassword' => 'test1234',
'appId' => $this->app->id
];
}
public function testCreateUserWithInvalidEmail() {
$this->body['email'] = 'this_is_not_a_valid_email_address';
$this->json('POST', '/profile/create', $this->body)
->assertResponseStatus(400);
}
}
Profile controller containing relevant code:
<?php
namespace App\Http\Controllers\Account;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\Account\CreateAccountPostRequest;
use App\Models\AccessToken;
use App\Models\User;
class ProfileController extends Controller
{
public function createUser(CreateAccountPostRequest $request) {
$user = new User();
$user->firstname = $request['firstName'];
$user->name = $request['name'];
$user->email = $request['email'];
$user->password = $request['password'];
$user->save();
$accessToken = AccessToken::createAccessToken($user->id);
$accessToken->save();
return $this->sendSuccessResponse();
}
}
Model factory:
$factory->define(App\Models\App::class, function (Faker\Generator $faker) {
$company = $faker->company;
return [
'name' => $company,
'submit_description' => $faker->sentence($nbWords = 6, $variableNbWords = true),
'subdomain' => str_replace(' ', '', $company),
'advancedFilter' => 0,
'isdeleted' => 0,
'defaultlanguage' => 'en',
'timestamp' => time()
];
});
Line 32 as stated by the stacktrace is the following line:
$this->json('POST', '/profile/create', $this->body)
->assertResponseStatus(400);
It all seems very straightforward according to the docs and I have no idea what is happening here.
Okay, I figured out what was going on here. I made a field protected $app; which accidentally overrides the $app field from the parent TestCase class. This converted the class from a Laravel app (Illuminate\Foundation\Application) to Illuminate/Database/Eloquent/Model on which the make() function does not exist. That's why the error was thrown.
So basically, renaming the $app field to $application or anything else resolves the problem.
I'm using Dingo API to create an API in Laravel 5.2 and have a controller returning data with
return $this->response->paginator($rows, new SymptomTransformer, ['user_id' => $user_id]);
However, I don't know how to retrieve user_id value in the SymptomTransformer! Tried many different ways and tried looking into the class but I'm relatively new to both Laravel and OOP so if anyone can point me to the right direction, it'd be greatly appreciated.
Below is my transformer class.
class SymptomTransformer extends TransformerAbstract
{
public function transform(Symptom $row)
{
// need to get user_id here
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
}
You can pass extra parameter to transformer constructor.
class SymptomTransformer extends TransformerAbstract
{
protected $extra;
public function __construct($extra) {
$this->extra = $exta;
}
public function transform(Symptom $row)
{
// need to get user_id here
dd($this->extra);
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
}
And call like
return $this->response->paginator($rows, new SymptomTransformer(['user_id' => $user_id]));
You can set extra param via setter.
class SymptomTransformer extends TransformerAbstract
{
public function transform(Symptom $row)
{
// need to get user_id here
dd($this->test_param);
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
public function setTestParam($test_param)
{
$this->test_param = $test_param;
}
}
And then:
$symptomTransformer = new SymptomTransformer;
$symptomTransformer->setTestParam('something');
return $this->response->paginator($rows, $symptomTransformer);
If you are using Dependency Injection, then you need to pass params afterwards.
This is my strategy:
<?php
namespace App\Traits;
trait TransformerParams {
private $params;
public function addParam() {
$args = func_get_args();
if(is_array($args[0]))
{
$this->params = $args[0];
} else {
$this->params[$args[0]] = $args[1];
}
}
}
Then you implement the trait in your transformer:
<?php
namespace App\Transformers;
use App\Traits\TransformerParams;
use App\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
use TransformerParams;
public function transform(User $user)
{
return array_merge([
'id' => (int) $user->id,
'username' => $user->username,
'email' => $user->email,
'role' => $user->roles[0],
'image' => $user->image
], $this->params); // in real world, you'd not be using array_merge
}
}
So, in your Controller, just do this:
public function index(Request $request, UserTransformer $transformer)
{
$transformer->addParam('has_extra_param', ':D');
// ... rest of the code
}
Basically, the trait is a bag for extra params.