Closed DigitalMachinist closed 1 year ago
Add actual reproducing code.
Sure. Does this give you enough to work with?
I can run ./vendor/bin/phpunit --filter=testOperatorHasManySessionsRelationship
and reproduce the issue with the setup described above.
The exact phpunit output is:
❯ ./vendor/bin/phpunit --filter=testOperatorHasManySessionsRelationship
PHPUnit 9.6.6 by Sebastian Bergmann and contributors.
E 1 / 1 (100%)
Time: 00:05.740, Memory: 28.00 MB
There was 1 error:
1) MyCompany\MyPackage\Data\Tests\Models\Operator\OperatorTest::testOperatorHasManySessionsRelationship
Error: Class "App\Operator" not found
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:767
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:412
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php:155
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:418
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:385
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:279
/mnt/c/Users/jrose/Workspace/git/my-package-name/tests/Unit/Models/Operator/OperatorTest.php:18
namespace MyCompany\MyPackage\Data\Tests\Models\Operator;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;
use MyCompany\MyPackage\Data\Models\Operator\Operator;
use MyCompany\MyPackage\Data\Models\Session\Session;
use MyCompany\MyPackage\Data\Tests\TestCase;
class OperatorTest extends TestCase
{
use RefreshDatabase;
public function testOperatorHasManySessionsRelationship(): void
{
$count = 2;
$operator = Operator::factory()->create();
Session::factory()->count($count)->create([
'operator_uuid' => $operator->uuid,
]);
$this->assertInstanceOf(HasMany::class, $operator->sessions());
$this->assertCount($count, $operator->sessions()->get());
}
}
namespace MyCompany\MyPackage\Data\Tests;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Orchestra\Testbench\TestCase as Orchestra;
class TestCase extends Orchestra
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->loadMigrationsFrom(__DIR__ . '/database/migrations');
Factory::guessFactoryNamesUsing(
fn (string $modelName) => 'MyCompany\\MyPackage\\Data\\Database\\Factories\\'.class_basename($modelName).'\\'.class_basename($modelName).'Factory'
);
}
protected function getPackageProviders($app)
{
return [
\MyCompany\MyPackage\Data\GamesServiceCoreDataModelServiceProvider::class,
\MyCompany\MyPackage\Data\Services\ApiKey\ApiKeyServiceProvider::class,
\MyCompany\MyPackage\Data\Services\DatabaseConnection\DatabaseConnectionServiceProvider::class,
\MyCompany\MyPackage\Data\Services\Lock\LockServiceProvider::class,
\MyCompany\MyPackage\Data\Services\OperatorModel\OperatorModelServiceProvider::class,
];
}
public function getEnvironmentSetUp($app)
{
// Make sure our .env file is loaded.
$app->useEnvironmentPath(__DIR__.'/..');
$app->bootstrapWith([LoadEnvironmentVariables::class]);
parent::getEnvironmentSetUp($app);
// Use mysql as our database connection.
config()->set('database.default', 'mysql');
config()->set('database.connections.mysql', [
'driver' => 'mysql',
'database' => env('DATABASE_NAME', 'testing'),
'host' => env('DATABASE_HOST', '127.0.0.1'),
'username' => env('DATABASE_USERNAME', ''),
'password' => env('DATABASE_PASSWORD', ''),
]);
}
}
namespace MyCompany\MyPackage\Data\Models\Operator;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Notifications\Notifiable;
use MyCompany\MyPackage\Data\Models\Session\Session;
use MyCompany\MyPackage\Data\Models\TransactionRequest\TransactionRequest;
use MyCompany\MyPackage\Data\Traits\SerializeBinaryUuids;
use Serializable;
use Spatie\BinaryUuid\HasBinaryUuid;
final class Operator extends Model implements Serializable
{
use HasBinaryUuid;
use HasFactory;
use Notifiable;
use SerializeBinaryUuids;
public const DEMO_OPERATOR_NAME = 'Demo';
//
// Settings
//
protected $primaryKey = 'uuid';
protected $keyType = 'string';
protected $dateFormat = 'c';
public $timestamps = true;
/**
* @var array<int, string>|null
*/
protected $binaryUuidFields = [
'uuid',
];
/**
* @var array<int, string>
*/
protected $fillable = [
'name',
'contact_email',
'base_url',
];
/**
* @var array<int, string>
*/
protected $hidden = [
'uuid',
'secret',
];
/**
* @var array<int, string>
*/
protected $dates = [
'started_at',
'ended_at',
'created_at',
'updated_at',
];
/**
* @var array<string, string>
*/
protected $casts = [
'status' => OperatorStatus::class,
];
//
// Boot
//
public static function boot(): void
{
parent::boot();
self::observe(new OperatorObserver());
}
//
// Custom Builder & Collection
//
/**
* @param \Illuminate\Database\Query\Builder $query
* @return OperatorBuilder
*/
public function newEloquentBuilder($query): OperatorBuilder
{
return new OperatorBuilder($query);
}
/**
* @param array<int, Operator> $models
* @return OperatorCollection
*/
public function newCollection(array $models = []): OperatorCollection
{
return new OperatorCollection($models);
}
//
// Relationships
//
/**
* @return HasMany<TransactionRequest>
*/
public function transaction_requests(): HasMany
{
return $this->hasMany(TransactionRequest::class);
}
/**
* @return HasMany<Session>
*/
public function sessions(): HasMany
{
return $this->hasMany(Session::class);
}
namespace MyCompany\MyPackage\Data\Database\Factories\Operator;
use MyCompany\MyPackage\Data\Models\Operator\Operator;
use MyCompany\MyPackage\Data\Models\Operator\OperatorStatus;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\MyCompany\MyPackage\Data\Models\Operator\Operator>
*/
class OperatorFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => Str::limit(fake()->company(), 16, ''),
'status' => OperatorStatus::ENABLED,
'contact_email' => fake()->email(),
'base_url' => url('api'),
'secret' => app(\MyCompany\MyPackage\Data\Services\ApiKey\ApiKeyServiceContract::class)->generate(64),
];
}
/**
* Indicate that the model's status should be set to 'disabled'.
*
* @return static
*/
public function disabled()
{
return $this->state(fn (array $attributes) => [
'status' => OperatorStatus::DISABLED,
]);
}
/**
* Indicate that the model's status should be set to 'disabled'.
*
* @return static
*/
public function enabled()
{
return $this->state(fn (array $attributes) => [
'status' => OperatorStatus::ENABLED,
]);
}
/**
* Indicate that the model's status should be set to 'disabled'.
*
* @return static
*/
public function demo()
{
return $this->state(fn (array $attributes) => [
'name' => Operator::DEMO_OPERATOR_NAME,
'contact_email' => 'gamesdemo@mycompany.com',
'base_url' => 'http://localhost',
'secret' => '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
]);
}
}
Error: Class "App\Operator" not found
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:767
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:412
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php:155
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:418
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:385
/mnt/c/Users/jrose/Workspace/git/my-package-name/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:279
Clearly, from above it was caused by Eloquent Factory and not Testbench. Testbench doesn't mess with any of the factory and would recommend using UserFactory::new()->create()
instead of User::factory()->create()
.
See https://github.com/orchestral/testbench-core/blob/8.x/src/Factories/UserFactory.php
As it turns out, it makes no difference whether I use Operator::factory()
or OperatorFactory::new()
but what does make a difference is explicitly defining the model type in the factory as you've done in UserFactory there: https://github.com/orchestral/testbench-core/blob/8.x/src/Factories/UserFactory.php#L20
Good enough to unblock me for now! Thank you for linking that. This might be a helpful thing to mention in docs as its something of a gotcha if you use model factories at all for testing.
Description:
I'm not sure that this is a bug, but I can't find any other useful sources about this, so here I am. I'm a long-time PHP/Laravel dev but this is my first time using testbench and doing package development.
I am developing a Laravel package that does not exist inside a full Laravel app skeleton -- its just requiring
orchestral/testbench
. I have configured mycomposer.json
to map my package namespace to my package rootsrc/
:However, when I run
./vendor/bin/phpunit
, I get the following error when using a factory to create any model:Obviously, this doesn't agree with the namespace I have given. However, my static analysis tools respect the namespace I've provided in
composer.json
. It seems like within my package, the namespace I have given is being handled correctly, but not by the LaravelApplication
instance.I've traced this back through how this namespace gets set, and it turns out that the
Application
instance is checking thecomposer.json
file oflaravel/laravel
for this value -- not the namespace I've given in the root of my package. I dumped out the contents of thecomposer.json
thatApplication
is reading when I try to create a model:Which makes sense if you're using
laravel/laravel
as a project skeleton, but not for package development.As far as I'm aware, I'm following all of the normal practices for testing a Laravel package using testbench, and I can't find any other cases on google for this issue I'm having. And yet... I can't see how this error shouldn't happen to everyone.
What am I doing wrong here?
Steps To Reproduce:
composer.json
with the package namespace as above.composer require --dev orchestral/testbench:^7 phpunit/phpunit:^9
composer install
./vendor/bin/phpunit