PHPUnit test can't find factory - laravel

I have a unit test that uses a JobFactory and ClientFactory.
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class JobTest extends TestCase
{
/** #test */
function can_display_job_details()
{
$job = create('App\Job', [
'job-number' => 9999,
'site' => 'Homeville',
'client_id' => function(){
return create('App\Client', [
'name' => 'ACME'
])->id;
},
]);
$details = $job->job_details;
$this->assertEquals('EPS-9999-Homeville-RES', $details);
}
}
When I run the test I get this error
InvalidArgumentException : Unable to locate factory with name [default] [App\Job].
I use the factory App\Job (JobFactory) in my feature test with no issues.
I use PhpStorm's built in PHPUnit testing function.
I run create and make through a little test helper
<?php
function create($class, $attributes = [], $times = null)
{
return factory($class, $times)->create($attributes);
}
function make($class, $attributes = [], $times = null)
{
return factory($class, $times)->make($attributes);
}
Not sure what I'm doing wrong but I can't seem to get to the bottom of it.
Any help greatly appreciated.

You are extending the wrong TestCase class.
Change:
use PHPUnit\Framework\TestCase;
to:
use Tests\TestCase;
Here you can find an example.

Related

PHP Unit Test Closure functions

I was performing unit test in my application, and some of the functions include closure functions like below:
<?php
namespace JobProgress\Transformers;
use League\Fractal\TransformerAbstract;
class CustomersTransformer extends TransformerAbstract {
public function includesCreatedBy($customer) {
$user = $customer->createdBy;
if($user) {
return $this->item($user, function($user){
\Log::info('unit test');
return [
'id' => (int)$user->id,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'full_name' => $user->full_name,
'full_name_mobile' => $user->full_name_mobile,
'company_name' => $user->company_name,
];
});
}
}
}
Note : I have extended TransformerAbstract class of Fractal and using the item function as $this->item from the TransformerAbstract class.
Here is how i am executing my test:
public function testIncludeDeletedBy() {
$customer = factory(Customer::class)->create();
$include = $this->transformer->includesCreatedBy($customer);
$object = $include->getData()->first();
$this->assertInstanceOf(User::class, $object);
}
My unit test is not executing the code that i have written in closure function.
Like as i have mentioned in above code, i have added some logs but my unit test is not executing that portion of the code.
Can anyone please help me
This might be related to the implementation detail of the $this->item() method. You may expect the closure to be executed while as we can see, the closure only needs to be parsed and then passed to the method as a parameter.
The code shows nothing that it is actually executed and therefore you should not expect this to be as it is an implementation detail not under your test.
You could de-anonymize the closure and call it directly to be able to test it as a unit.
So you have one test for the if-clause in the existing method and one test which allows you to run (not only parse) the code you currently have as an anonymous function / closure.
This is sometimes called a test-point and can bring you up speed. The smaller the units you have are, the easier it is to test them.
<?php
namespace JobProgress\Transformers;
use League\Fractal\TransformerAbstract;
class CustomersTransformer extends TransformerAbstract {
public function includesCreatedBy($customer) {
$user = $customer->createdBy;
if($user) {
return $this->item($user, $this->transformImplementation(...));
}
}
public function transformImplementation(object $user) {
\Log::info('unit test');
return [
'id' => (int)$user->id,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'full_name' => $user->full_name,
'full_name_mobile' => $user->full_name_mobile,
'company_name' => $user->company_name,
];
}
}
For the three dots (...), this is PHP 8.1 syntax, compare PHP RFC: First-class callable syntax for alternative PHP syntax before 8.1 for callables, e.g.:
Closure::fromCallable([$this, 'transformImplementation'])

Laravel dusk with browserstack to run tests on multiple devices and browsers

I am using this ("browserstack/browserstack-local": "^1.1") package to run dusk tests on BrowserStack. Now the requirement is to run tests on multiple and different devices with different browsers. Currently, I am following this approach to run tests.
private function browserStackCaps($local_identifier)
{
return [
'project' => config('app.name'),
'browserstack.local' => 'true',
'browser' => env('BROWSER'),
'device' => env('DEVICE'),
'acceptSslCert' => true,
'resolution' => '1920x1080'
];
}
The drawback of this approach is I have to change the device name and browser name in the .env file every time I need to run tests on a different device/browser. Is there any way I can run tests on the provided array? The array that contains devices and browser information.
I know this is old, but I found this page while searching for a solution. I ended up building one myself that would probably meet your use-case. The biggest hurdle that I had was $this->browse() in a normal Dusk test was using a single instance of Laravel\Dusk\Browser and the new capabilities were not being pulled in. This implementation adds a function called performTest to the DuskTestCase.php file. This function loops through a set of capabilities and instantiates a new instance of Laravel\Dusk\Browser for each test. This function works similarly to the existing browse function in Laravel Dusk. You call performTest by passing it a callable that accepts a single parameter which is an instance of Laravel\Dusk\Browser
Dusk Test Case
<?php
namespace Tests;
use Laravel\Dusk\TestCase as BaseTestCase;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
abstract class DuskTestCase extends BaseTestCase
{
use CreatesApplication;
protected array $capabilities;
private const BROWSERS = [
'ios_14_iphone_xs_safari' => [
"os_version" => "14",
"device" => "iPhone XS",
"real_mobile" => "true",
"browserstack.local" => "true",
'acceptSslCerts' => 'true'
],
'mac_osx_catalina_safari' => [
"os" => "OS X",
"os_version" => "Catalina",
"browser" => "Safari",
"browser_version" => "13.0",
"browserstack.local" => "true",
"browserstack.selenium_version" => "3.14.0",
"resolution" => "1920x1080",
'acceptSslCerts' => 'true',
]
];
/**
* Create the RemoteWebDriver instance.
*
* #return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$browserStackConnectionUrl = config('browserstack.connection_url');
return RemoteWebDriver::create(
$browserStackConnectionUrl, $this->capabilities
);
}
protected function performTest(Callable $test){
foreach(self::BROWSERS as $browserName => $capabilitySet){
try {
$this->capabilities = $capabilitySet;
$browser = $this->newBrowser($this->driver());
$test($browser);
$browser->quit();
fprintf(STDOUT, "\e[0;32m√ {$browserName}\r\n");
}
catch(\Exception $exception){
fprintf(STDOUT, "\e[0;31mX {$browserName}\r\n");
throw $exception;
}
}
}
}
Example Test
<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
class ExampleTest extends DuskTestCase
{
public function testExample()
{
$this->performTest(function(Browser $browser){
$browser->visit('/')
->assertDontSee('Foobar');
});
}
}
config/browserstack.php
<?php
return [
'connection_url' => env('BROWSERSTACK_CONNECTION_URL')
];
you can implement this at your end. Fetch the list of Browsers and Devices you want to execute your tests on using the REST API and use the same.
REST API to be used:
curl -u "username:password"
https://api.browserstack.com/automate/browsers.json
Read more on this here:
https://www.browserstack.com/docs/automate/api-reference/selenium/browser#get-browser-list

SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘proposal_db.userlogs’ doesn’t exist

I’m doing some customization inside the CakeDC/users plug-in. I created a table with name “user_logs” which consist of foreign key relationship with the actual “users” table provided by CakeDC/users.
I baked the “user_logs” model using command:
bin\cake bake model UserLogs --plugin CakeDC/Users
After user gets login I’m just generating log transaction inside the “user_logs” table. I added the following line inside the “/vendor/cakedc/users/src/Controller/Traits/LoginTrait.php” file under _afterIdentifyUser function:
$this->activity_log(‘Login’, ‘Login’, $user[‘id’]);
And activity_log function is added inside the src/Controller/AppController.php file:
function activity_log($page, $action, $id=null){
$this->loadModel(‘CakeDC/Users.Userlogs’);
$dataUserLog = $this->Userlogs->newEntity();
$dataUserLog['user_id'] = $this->request->session()->read('Auth.User.id');
if(!empty($id)){
$dataUserLog['reference_id'] = $id;
} else {
$dataUserLog['reference_id'] = 0;
}
$dataUserLog['activity_timestamp'] = date('Y-m-d H:i:s');
$dataUserLog['page'] = $page;
$dataUserLog['action'] = $action;
$this->Userlogs->save($dataUserLog);
}
vendor/cakedc/users/src/Model/Entity/UserLog.php file code:
namespace CakeDC\Users\Model\Entity;
use Cake\ORM\Entity;
class UserLog extends Entity
{
protected $_accessible = [
‘user_id’ => true,
‘reference_id’ => true,
‘activity_timestamp’ => true,
‘page’ => true,
‘action’ => true,
‘user’ => true
];
}
vendor/cakedc/users/src/Model/Table/UserLogsTable.php file code:
namespace CakeDC\Users\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UserLogsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('user_logs');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'className' => 'CakeDC/Users.Users'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmptyString('id', null, 'create');
$validator
->dateTime('activity_timestamp')
->allowEmptyDateTime('activity_timestamp');
$validator
->scalar('page')
->maxLength('page', 255)
->allowEmptyString('page');
$validator
->scalar('action')
->maxLength('action', 255)
->allowEmptyString('action');
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['user_id'], 'Users'));
return $rules;
}
}
The surprise part is! this works on localhost but when I’m uploading code on a server it’s not working. On localhost I’ve PHP v7.3.4 and on server I’ve PHP v5.6.40. Can any one suggest what’s wrong with this why it’s working on localhost and not on server? Everything is same I’ve done almost everything cleared model cache on server as well but no luck. Please help.
Not really sure why CAKEPHP is looking for table “proposal_db.userlogs” on server whereas I created “user_logs” table on both local and server. Please suggest?

How to mock authentication user on unit tests with Codeception in Laravel 5?

I have to convert my unit tests to codeception. I need to use loginWithFakeUser() function from this article - How to mock authentication user on unit test in Laravel?
public function loginWithFakeUser() {
$user = new User([
'id' => 1,
'name' => 'yish'
]);
$this->be($user);
}
How can I use the $this->be() when my class is already extending \Codeception\Test\Unit? I don't know what should I use .. or how to use properly. Putting the function loginWithFakeUser() inside this:
use Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication;
use Illuminate\Foundation\Testing\Concerns\InteractsWithSession;
class AdminTest extends \Codeception\Test\Unit {
use InteractsWithAuthentication;
use InteractsWithSession;
}
Gives me an error:
[ErrorException] Undefined property: AdminTest::$app
I'm not sure how can I set the $app variable. Please help me. Thanks a lot!
I was able to solve this by mocking the Auth class.
$oUser = new User([
'id' => 1,
'name' => 'yish'
]);
Auth::shouldReceive('check')->once()->andReturn(true);
Auth::shouldReceive('user')->once()->andReturn($oUser);
Where in my actual code it uses it as:
if(Auth::check() === true) {
$sName = Auth::user()->name;
}

How to correctly instantiate the Iluminate class outside of laravel

I have Eloquent working outside of Laravel with no problems. Now Im trying to use also the Validation class by:
<?php
namespace User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Validator;
class User extends Model {
private $rules = array(
'firstName' => 'required|min:2|max:50',
'lastName' => 'required|min:2|max:50',
);
public function validate($data)
{
$v = Validator::make($data, $this->rules);
return $v->passes();
}
}
Executing that code give me an error:
Call to undefined method Illuminate\Validation\Validator::make()
That is correct since the method make is not on the class Validator but in his factory:
How can I correctly instantiate the Validation lib in order to get this working?
BTW, this is my composer.json:
{
"require": {
"slim/slim": "^2.6",
"illuminate/database": "^5.1",
"illuminate/validation": "^5.1"
},
"autoload": {
"classmap": [
"app/model"
]
}
}
To get the validation package to work outside of Laravel, you'll also need the translation package.
So first create an instance of the translator, and then use that to make a validation factory.
Working example
<?php
/*
Required composer packages:
illuminate/validation
illuminate/translation
*/
/*
Translation language files directory is the same as with Laravel
./lang/en/validation.php
*/
require_once 'vendor/autoload.php';
// You need to specify where the translation files is
$test_translation_path = __DIR__.'/lang';
$test_translation_locale = 'en';
// Set up data for the validator
$test_input_data = ['field' => 'value'];
$test_input_rules = ['field' => 'required'];
$translation_file_loader = new Illuminate\Translation\FileLoader(new Illuminate\Filesystem\Filesystem, $test_translation_path);
$translator = new Illuminate\Translation\Translator($translation_file_loader, $test_translation_locale);
$validation_factory = new Illuminate\Validation\Factory($translator);
$validator = $validation_factory->make($test_input_data, $test_input_rules);
if ($validator->fails()) {
die('Validation failed');
}
die('Validation passed!');
There are a few issues with your approach:
you're trying to create a new Validator instance by calling the Illuminate\Validation\Validator::make method, yet you point out that the make() method is present on the Illuminate\Validation\Factory which is a different class altogether, so the error you're getting is justified.
you're trying to call the make() method statically :: when in fact it's not defined as such.
you're trying to use the Validator the same as you would in a Laravel application enviroment, which won't work because you're missing the Laravel Facades and Service Providers infrastructure that Laravel uses to allow for such a simple instantiation of the Validator.
If you were to look at the registerValidationFactory() method inside the Illuminate\Validation\ValidationServiceProvider class, you'd get a sense of how the validator instance is created. So based on that, you could do the following:
namespace User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Factory as ValidatorFactory;
use Symfony\Component\Translation\Translator;
class User extends Model {
private $rules = array(
'firstName' => 'required|min:2|max:50',
'lastName' => 'required|min:2|max:50',
);
public function validate($data)
{
$factory = new ValidatorFactory(new Translator('en'));
$v = $factory->make($data, $rules);
return $v->passes();
}
}

Resources