pestphp / pest

Pest is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP.
https://pestphp.com
MIT License
9.08k stars 317 forks source link

Laravel's factory not instantiating Faker properly on Pest tests #818

Closed ValterJunior closed 1 year ago

ValterJunior commented 1 year ago

Hello,

I have been trying to start using pest in an existing Laravel project, although already existing, still a small one. I started by following the library documentation and tried to migrate my first test to this new format. However, for some reason, pest won't properly instantiate the Faker object from my factory classes on Laravel.

Here is how the test looks like:

use App\Models\Battery;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use function Pest\Laravel\{getJson};

uses(DatabaseTransactions::class)->in(__DIR__);

it('can list batteries', function () {
    Battery::factory(30)->create();

    getJson(route('batteries.index'))
        ->assertOk()
        ->assertJsonCount(20, 'data');
});

When running this test using 'sail pest' command (or 'php artisan pest'), I get the following error:

  ⨯ it can list batteries                                                                         0.05s  
  ─────────────────────────────────────────────────────────────────────────────────────────────────────  
   FAILED  Tests\Feature\BatteryApiTest > it can list batteries               InvalidArgumentException   
  Unknown format "name"

  at vendor/fakerphp/faker/src/Faker/Generator.php:731
    727▕                 return $this->formatters[$format];
    728▕             }
    729▕         }
    730▕ 
  ➜ 731▕         throw new \InvalidArgumentException(sprintf('Unknown format "%s"', $format));
    732▕     }
    733▕ 
    734▕     /**
    735▕      * Replaces tokens ('{{ tokenName }}') with the result from the token method call

      +3 vendor frames 
  4   database/factories/BatteryFactory.php:21
      +8 vendor frames 
  13  tests/Feature/BatteryApiTest.php:16

  Tests:    1 failed (0 assertions)
  Duration: 0.17s

Comparing the behavior of both original (PHPUnit) and migrated (Pest) ones, I can see that at some point the base factory class from Laravel will try to instantiate the Faker generator class using the service container, and at this point, in the original process the DatabaseServiceProvider should be called which will then call the 'create' method of the Generator class, as you can see from the stack trace captured below:

  0 => array:6 [
    "file" => "/var/www/html/vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php"
    "line" => 93
    "function" => "create"
    "class" => "Faker\Factory"
    "type" => "::"
    "args" => array:1 [
      0 => "en_US"
    ]
  ]
  1 => array:6 [
    "file" => "/var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php"
    "line" => 910
    "function" => "Illuminate\Database\{closure}"
    "class" => "Illuminate\Database\DatabaseServiceProvider"
    "type" => "->"
    "args" => array:2 [
      0 => Illuminate\Foundation\Application^ {#984 …40}
      1 => array:1 [
        "locale" => "en_US"
      ]
    ]
  ]
  2 => array:6 [
    "file" => "/var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php"
    "line" => 795
    "function" => "build"
    "class" => "Illuminate\Container\Container"
    "type" => "->"
    "args" => array:1 [
      0 => Closure($app, $parameters)^ {#103
        class: "Illuminate\Database\DatabaseServiceProvider"
        this: Illuminate\Database\DatabaseServiceProvider {#930 …}
      }
    ]
  ]
  3 => array:6 [
    "file" => "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Application.php"
    "line" => 933
    "function" => "resolve"
    "class" => "Illuminate\Container\Container"
    "type" => "->"
    "args" => array:3 [
      0 => "Faker\Generator"
      1 => array:1 [
        "locale" => "en_US"
      ]
      2 => true
    ]
  ]
  4 => array:6 [
    "file" => "/var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php"
    "line" => 731
    "function" => "resolve"
    "class" => "Illuminate\Foundation\Application"
    "type" => "->"
    "args" => array:2 [
      0 => "Faker\Generator"
      1 => array:1 [
        "locale" => "en_US"
      ]
    ]
  ]
]

The same does not happen when running the factory with the new Pest test. For some reason, Laravel's container service does not call the DatabaseServiceProvider at the same point, which makes me wonder whether Laravel framework is being properly booted at all, in this case.

Here is how the factory class looks like, in case you need more insight on that:

<?php

namespace Database\Factories;

use App\Models\Battery;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends Factory<Battery>
 */
class BatteryFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => $this->faker->name(),
            'description' => $this->faker->paragraph(),
            'level' => $this->faker->numberBetween(1, 50),
            'capacity' => $this->faker->numberBetween(1, 10),
            'created_at' => now(),
            'updated_at' => now()
        ];
    }
}

...and here is the composer.json library list. As you can see, I have both pestphp/pest and pestphp/pest-plugin-laravel libraries installed.

    "require": {
        "php": "^8.1",
        "guzzlehttp/guzzle": "^7.2",
        "laravel/framework": "^10.0",
        "laravel/sanctum": "^3.2",
        "laravel/tinker": "^2.7",
        "prettus/l5-repository": "^2.9"
    },
    "require-dev": {
        "fakerphp/faker": "^1.9.1",
        "laravel/sail": "^1.22",
        "mockery/mockery": "^1.4.4",
        "nunomaduro/collision": "^7.0",
        "pestphp/pest": "^2.6",
        "pestphp/pest-plugin-laravel": "^2.0",
        "phpunit/phpunit": "^10.0",
        "spatie/laravel-ignition": "^2.0"
    },

Any clue on what could be happening in here? Any tips are much appreciated.

Thanks, Valter

ValterJunior commented 1 year ago

Actually, I just found my solution. Adding uses(Tests\TestCase::class)->in('Feature'); in the Pest.php file instead of my own testing file did the trick. I don't know if that's mentioned in the documentation, but at least it wasn't that clear to me.

I will leave it here in case someone stumbles with the same issue.