Laravel 8 Tests: PHPUnit error: Unknown formatter "unique" - laravel

I have written a test that involves a factory. When I execute the test, I get this error:
The data provider specified for Tests\Unit\ExampleTest::testTakePlace is invalid. InvalidArgumentException: Unknown formatter "unique" /var/www/html/api/vendor/fakerphp/faker/src/Faker/Generator.php:249
Expected result
This error should not be shown, I should be able to use $this->faker->unique().
How I tried to fix this problem
By reading the doc again and again (no difference was found) and by reading questions&answers on the Internet (only one question & only one answer were found: to extend Laravel's TestCase but the official documentation, as I mentionned it, says the contrary). (Laravel's TestCase is provided by use Illuminate\Foundation\Testing\TestCase;)
Question
Why doesn't it work and how to fix this bug?
Sources
Test sources
It extends PHPUnit\Framework\TestCase (not Laravel's TestCase) because the documentation says to extend it. Indeed: https://laravel.com/docs/8.x/testing#creating-and-running-tests . This is not the only part of the doc mentionning to extend it.
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Models\Project;
class ExampleTest extends TestCase
{
/**
* #dataProvider provideTakePlaceData
*/
public function testTakePlace($project)
{
$response = $this->json('GET', '/controllerUserProject_takePlace', [
'project_id' => $project->id
]);
}
public function provideTakePlaceData() {
return [
Project::factory()->make()
];
}
}
Controller
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ControllerUserProject extends Controller
{
public function takePlace(Request $request, $project_id)
{
return;
}
}
The most important: the factory
<?php
namespace Database\Factories;
use App\Models\Project;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
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()
{
return [
'id' => $this->faker->unique()->numberBetween(1, 9000),
];
}
}

Change:
use PHPUnit\Framework\TestCase;
to:
use Tests\TestCase;
Why?
When your ExampleTest extends PHPUnit\Framework\TestCase Laravel app is never initialized in tests, and so you don't have access to Laravel features like factories.
The documentation tells you to extend PHPUnit\Framework\TestCase;, but it refers to Unit tests. Feature tests should extend Tests\TestCase. This is something pretty new. Until Laravel 5.8 both unit and feature tests were extending Tests\TestCase by default. I personally just define all tests as feature tests to avoid such issues.

The problem was due to the use of a dataProvider with the use of the factories. More precisely: PHPUnit data providers should not be used when Laravel factories are.

If you are using faker in Factories please make sure these are working correctly regardless (as per example try running Project::factory()->make(); within Laravel Tinker and see the results)
Second common issue (as mentioned above) is the class that you extend your Test with - see above
Third and frequently omitted one that's causing this error, is a missing parent constructor call in the setUp() - (if you use it)
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Models\Project;
class ExampleTest extends TestCase
{
protected Project $project;
public function setUp(): void
{
parent::setUp();
$this->project = Project::factory()->make();
}

I think you need to use the WithFaker trait to use faker :
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Models\Project;
use Illuminate\Foundation\Testing\WithFaker;
class ExampleTest extends TestCase
{
use WithFaker;
// ...
}

Related

How to mock laravel model relathipship?

I have a model that has a relationship with a View, that is complicate to popolate for make the feature test, but in the same time this is called from some component that are inside the controller called.
The following code is an example:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\TemperatureView;
class Town extends Model
{
function temperature()
{
return $this->hasOne(TemperatureView::class);
}
}
This is an example of the controller:
<?php
namespace App\Http\Controllers;
use App\Models\Town;
class TownController extends Controller
{
public function update($id)
{
// Here is the validation and update of Town model
$UpdatedTown = Town::where('id',$id);
$UpdatedTown->update($data);
$this->someOperation($UpdatedTown);
}
private function someOperation($Town)
{
//Here there is some operation that use the temperature Relationship
/*
Example:
$Town->temperature->value;
*/
}
}
The test is like is like this:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
use App\Models\TownModel;
use Mockery;
use Mockery\MockInterface;
class TownTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function test_get_town_temperature()
{
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test failed
}
public function test_get_town_temperature_with_mocking()
{
$this->instance(
TownModel::class,
Mockery::mock(TownModel::class, function (MockInterface $mock) {
$MockDataTemperature = (object) array('value'=>2);
$mock->shouldReceive('temperature')->andReturn($MockDataTemperature);
})
);
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test also failed
}
}
The first test failed because the Controller has some check on the relationship temperature, that is empty because the view on database is empty.
The second test failed also for the same reason. I tried to follow some others questions with the official guide of Laravel Mocking. I know this is mocking object and not specially Eloquent.
Is something I'm not setting well?
If it's not possible to mock only the function, is possible to mock all the relationship of view, bypassing the DB access to that?
Edit
I undestand that the mocking work only when the class is injected from laravel, so what I wrote above it's not pratical.
I don't know if it's possible to mock only it, I saw a different option, that to create the interface of the model and change for the test, but I didn't want to make it.

Lumen - Using factory() function in tests make error - Undefined Function

I'm using Lumen 8.3 ,wanted to use factory() function in my tests, it gives me
Undefined Function ,there is nothing useful in the Docs of Lumen
Am i missing something here?
class ProductTest extends TestCase
{
public function test_if_can_send_products_list(){
$products = factory('App/Products',5)->make();
$this->json('post','/payouts',$products)
->seeJson([
'created' => true,
]);
}
}
->
Error: Call to undefined function factory()
It's better to use direct class like that:
$products = factory(Products::class, 5)->create();
don't forget to add Products model usage (namespace).
Edit
You should create Factory:
<?php
namespace Database\Factories;
use App\Products;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class ProductFactory extends Factory
{
protected $model = Products::class;
public function definition(): array
{
return [
'name' => $this->faker->unique()->userName()
];
}
}
And add HasFactory Trait to your model:
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Products extends Model {
use HasFactory;
}
you can also use it like this
Products::factory()->count(5)->make();
I just uncommented these lines in app.php file
$app->withFacades();
$app->withEloquent();
Apparently Laravel 8 removed the 'factory' helper, and it seems Lumen followed that path without updating documentation;
#Faesal Answer is the correct way to do it these days;
remember to add use HasFactory; to your Model.

Laravet test case failing, unable to run seed on test case

My test case:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use Illuminate\Http\Response;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class UserTest extends TestCase
{
use RefreshDatabase, WithFaker, DatabaseMigrations;
protected $endPoint = '/dashboard/users';
public function setUp():void
{
parent::setUp();
$this->artisan('migrate:fresh');
$this->artisan('db:seed');
}
public function test_users_list_is_showing_correctly()
{
$this->signIn();
$this->get($this->endPoint)
->assertStatus(Response::HTTP_OK);
}
}
But, I am receiving error:
SQLSTATE[HY000]: General error: 1 no such table: settings (SQL: select "id", "name", "setting_value" from "settings")
It might be throwing error because, I have this code in boot method of AppServiceProvider
config(['settings' => Setting::get(['id','name','setting_value'])]);
How to fix this? Its probably that migration and seeder are not working, but not sure.
Assuming that you are using laravel 8.x and as it can be seen in code that you have used RefreshDatabase trait. You can do something like this:
// Run the DatabaseSeeder...
$this->seed();
// Run a specific seeder...
$this->seed(OrderStatusSeeder::class);
The above code is taken from the official documentation.
Alternatively, you can set
protected $seed = true;
in your base test case class. This will run the seeder before each test that uses RefreshDatabase trait.
You can also define which seeder should run by specifying this
protected $seeder = OrderStatusSeeder::class;
in your test class.
Hope this helps. More information you find here.

Build a Laravel App With TDD - Episode 4, InvalidArgumentException: Unable to locate factory with name [default] [App\Project]

I am deep in bug town here, can anyone help?
I am following the tutorial line by line and have got as far as the unit test in the episode:
https://laracasts.com/series/build-a-laravel-app-with-tdd/episodes/4
I get the error:
1) Tests\Unit\ProjectTest::test_it_has_a_path
InvalidArgumentException: Unable to locate factory with name [default] [App\Project].
Some info:
The unit test is the second test in the video (starts # 4:18), my
code works fine for the first (feature) test, and the factory
function works fine in tinker.
I have tried all kinds of different filepaths for 'App\Project', and
also tried static Project::class type references.
My phpunit didn't run tests unless I prefixed the word 'test', even
when filtering to the exact test name, so I believe this may be due
to an underlying phpunit version difference.
My unit test:
namespace Tests\Unit;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\TestCase;
class ProjectTest extends TestCase
{
use RefreshDatabase;
public function test_it_has_a_path()
{
$project = factory('App\Project')->create();
$this->assertEquals('/project/' . $project->id, $this->path());
}
}
My Project factory
/** #var \Illuminate\Database\Eloquent\Factory $factory */
use App\Project;
use Faker\Generator as Faker;
$factory->define(Project::class, function (Faker $faker) {
return [
'title' => $faker->sentence,
'description' => $faker->sentence
];
});
My show method
public function show(Project $project)
{
return view('projects.show', compact('project'));
}
My route
Route::get('projects/{project}', 'ProjectsController#show');
and the working feature test (for reference)
public function test_a_user_can_view_a_project()
{
$this->withoutExceptionHandling();
$project = factory('App\Project')->create();
$this->get("/projects/" . $project->id)
->assertSee($project->title)
->assertSee($project->description);
}
I got this answer on the Laracasts forum that solved my problem:
the reason is because the unit test now extends from the PHPUnit testcase class instead of the framework testcase..
So you should avoid using factory in Unit tests. This was a change recently..
To be able to still use the factory, you should import this TestCase.
use Tests\TestCase;
And remove this:
use PHPUnit\Framework\TestCase;
And here is the change:
https://github.com/laravel/framework/commit/e30a0c979d98f2f1f7b6c565e4002734237a280b

Laravel - How to use faker in PHPUnit test?

It's giving me this error when I run the test:
undefined variable $faker.
This is the WithFaker file.
https://github.com/laravel/framework/blob/5.5/src/Illuminate/Foundation/Testing/WithFaker.php
<?php
namespace Tests\Unit;
use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class LoginTest extends TestCase
{
use WithFaker;
/**
* A basic test example.
*
* #return void
*/
/** #test */
public function test_example()
{
$user = User::create([
'username' => $faker->firstName(),
]);
}
}
You have to use $this->faker->firstName() not just $faker->firstName()
Update 1
Now when we use WithFaker Trait $this->faker will give us null, to get around this make sure to call $this->setupFaker() first.
e.g.
class SomeFactory
{
use WithFaker;
public function __construct()
{
$this->setUpFaker();
}
}
credit #Ebi
For anyone coming here from 2021. We no longer require the addition of
$this->setUpFaker();
You only need to include the trait as described in the accepted answer.
once you completed installation of Faker.
include autoload file and create instance
$faker = \Faker\Factory::create();
$faker->firstname()
$faker->lastname()
For more information visit
check you seed function run (Faker $faker).

Resources