cwadigital's avatar

Error: Model not found when using factories in package tests

I'm receiving this error when running tests in a Laravel 8 package I'm developing:

Error: Class 'App\Product' not found

/package/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:625
/package/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:345
/package/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php:157
/package/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:350
/package/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:318
/package/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:230
/package/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:227
/package/tests/Unit/ProductTest.php:30

It seems somewhere along the line the Factory is using the wrong namespace to try and use the Product model.

Here is the Factory:

// database/factories/ProductFactory.php
<?php

namespace Me\MyPackage\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Me\MyPackage\Models\Product;

class ProductFactory extends Factory
{
    /**
     * The name of the factory's corresponding product.
     *
     * @var string
     */
    protected $product = Product::class;

    /**
     * Define the product's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
          'name' => $this->faker->words(3, true)
        ];
    }
}

And my Model looks like so:

// src/Models/Product.php
<?php

namespace Me\MyPackage\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Product extends Model
{
    use HasFactory;

    protected $guarded = [];

    protected static function newFactory()
    {
        return \Me\MyPackage\Database\Factories\ProductFactory::new();
    }
}

My test looks like so:

// tests/Unit/ProductTest.php
<?php

namespace Me\MyPackage\Tests\Unit;

use Me\MyPackage\Models\Product;
use Me\MyPackage\Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ProductTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    function a_product_has_a_name()
    {
        $product = Product::factory()->create([
            'name' => 'Fake name',
        ]);
        
        $this->assertEquals('Fake name', $product->name);
    }
}

Here is my TestCase:

// tests/TestCase.php
<?php

namespace Me\MyPackage\Tests;

use Me\MyPackage\MyPackageServiceProvider;

class TestCase extends \Orchestra\Testbench\TestCase
{
  public function setUp(): void
  {
    parent::setUp();
    // additional setup
  }

  protected function getPackageProviders($app)
  {
    return [
      MyPackageServiceProvider::class,
    ];
  }

  protected function getEnvironmentSetUp($app)
  {
    include_once __DIR__ . '/../database/migrations/create_products_table.php.stub';

    (new \CreateProductsTable)->up();
  }
}

And finally here's my composer.json file:

{
    "name": "me/my-package",
    "description": "A package for Laravel",
    "type": "library",
    "license": "MIT",
    "authors": [
      {
        "name": "Me",
        "email": "[email protected]"
      }
    ],
    "require": {
      "orchestra/testbench": "^6.13"
    },
    "autoload": {
      "psr-4": {
        "Me\MyPackage\": "src",
        "Me\MyPackage\Database\Factories\": "database/factories",
        "Me\MyPackage\Tests\": "tests"
      }
    },
    "require-dev": {
      "phpunit/phpunit": "^9.5"
    },
    "extra": {
      "laravel": {
        "providers": [
          "Me\MyPackage\MyPackageServiceProvider"
        ]
      }
    },
    "scripts": {
      "test": "vendor/bin/phpunit",
      "test-f": "vendor/bin/phpunit --filter"
    }
  }

And I have definitely run composer dump-autoload multiple times as I've been trying various things.

Often with things like this it turns out to be a simple namespacing issue, but I'm at the point now where I've checked all the namespaces and gone through the whole thing with a fine tooth comb, and still the error persists.

Digging a bit deeper into the trace, in vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php the modelName() method is returning App\Product rather than Me\MyPackage\Models\Product so something's breaking down along the way.

// vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:633
public function modelName()
    {
        $resolver = static::$modelNameResolver ?: function (self $factory) {
            $factoryBasename = Str::replaceLast('Factory', '', class_basename($factory));

            $appNamespace = static::appNamespace();

            return class_exists($appNamespace.'Models\'.$factoryBasename)
                        ? $appNamespace.'Models\'.$factoryBasename
                        : $appNamespace.$factoryBasename;
        };
        dd($this->model ?: $resolver($this)); // Debugging added by me
        return $this->model ?: $resolver($this);
    }

Do I have to inject something to tell appNamespace to return my package namespace when resolving this?

Any ideas or help would be much appreciated, as I say I've been through this numerous times now and can't find anyone else online having the same issue, and as far as I can see I have everything set up correctly.

Thanks in advance

0 likes
3 replies
jlrdw's avatar

Is Me\MyPackage\Models\Product; correct or is it src/Models/Product.php

cwadigital's avatar

Me\MyPackage is mapped to the src directory in my composer.json, so I would assume they are the same? So Me\MyPackage\Models is the namespace for src/Models

Sinnbeck's avatar

I think this should be $model

protected $product = Product::class;
//should be 
protected $model = Product::class;
1 like

Please or to participate in this conversation.