we failed to identify your request PHPUNIT - laravel-5

I have a test function -
class InstrumentTest extends TestCase {
private $faker;
public function setUp() {
$this->refreshApplication();
$this->faker = Faker\Factory::create('en_EN');
$user = App\User::find(2);
$this->be($user);
}
/**
* Test creating new instrument
*/
public function testCreateNewInstrument() {
$fakeName = $this->faker->name;
$this->visit('/oem/instrument/create')
->type($fakeName, 'name')
->press('Create')
->seePageIs('/oem/instrument')
->see('Instrument Created Successfully!');
# make sure that record is in the database
$this->seeInDatabase('instrument', ['name' => $fakeName, 'company_id' => 1]);
}
}
Each time I run "phpunit" my test die with following message in the console:
"We failed to identify your request."
I am not sure on how to fix this and if this is related to the middleware?

Solution TestCase.php has a $baseUrl value that has to be your domain name

Related

How to change hard coded Eloquent $connection only in phpunit tests?

I have an Eloquent Model like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class SomeModel extends Model
{
protected $connection = 'global_connection';
......................
The problem is that this $connection has to be hard coded because I have a multi tenant web platform and all the tenants should read from this Database.
But when now in tests I am hitting the Controller route store() and I don't have access to the model!
I just do this:
public function store()
{
SomeModel::create($request->validated());
return response()->json(['msg' => 'Success']);
}
Which works great when using it as a user through browser...
But now I want to somehow force that model NOT to use that hard coded $connection and set it to Testing database connection...
And this is my Test
/** #test */
public function user_can_create_some_model(): void
{
$attributes = [
'name' => 'Some Name',
'title' => 'Some Title',
];
$response = $this->postJson($this->route, $attributes)->assertSuccessful();
}
Is there any way to achieve this with some Laravel magic maybe :)?
Because you asked for Laravel magic... Here it goes. Probably an overkill and over engineered way.
Let's first create an interface whose sole purpose is to define a function that returns a connection string.
app/Connection.php
namespace App;
interface Connection
{
public function getConnection();
}
Then let's create a concrete implementation that we can use in real world (production).
app/GlobalConnection.php
namespace App;
class GlobalConnection implements Connection
{
public function getConnection()
{
return 'global-connection';
}
}
And also another implementation we can use in our tests.
app/TestingConnection.php (you can also put this in your tests directory, but make sure to change the namespace to the appropriate one)
namespace App;
class TestingConnection implements Connection
{
public function getConnection()
{
return 'testing-connection';
}
}
Now let's go ahead and tell Laravel which concrete implementation we want to use by default. This can be done by going to the app/Providers/AppServiceProvider.php file and adding this bit in the register method.
app/Providers/AppServiceProvider.php
namespace App\Providers;
use App\Connection;
use App\GlobalConnection;
// ...
public function register()
{
// ...
$this->app->bind(Connection::class, GlobalConnection::class);
// ...
}
Let's use it in our model.
app/SomeModel.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class SomeModel extends Model
{
public function __construct(Connection $connection, $attributes = [])
{
parent::__construct($attributes);
$this->connection = $connection->getConnection();
}
// ...
}
Almost there. Now in our tests, we can replace the GlobalConnection implementation with the TestingConnection implementation. Here is how.
tests/Feature/ExampleTest.php
namespace Tests\Feature;
use Tests\TestCase;
use App\Connection;
use App\TestingConnection;
class ExampleTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
$this->app->instance(Connection::class, TestingConnection::class);
}
/** #test */
public function your_test()
{
// $connection is 'testing-connection' in here
}
}
Code is untested, but should work. You can also create a facade to access the method statically then use Mockery to mock the method call and return a desired connection string while in testing.
Unfortunately for me, none of these answers didn't do the trick because of my specific DB setup for multi tenancy. I had a little help and this is the right solution for this problem:
Create a custom class ConnectionResolver somewhere under tests/ directory in laravel
<?php
namespace Tests;
use Illuminate\Database\ConnectionResolverInterface;
use Illuminate\Database\ConnectionResolver as IlluminateConnectionResolver;
class ConnectionResolver extends IlluminateConnectionResolver
{
protected $original;
protected $name;
public function __construct(ConnectionResolverInterface $original, string $name)
{
$this->original = $original;
$this->name = $name;
}
public function connection($name = null)
{
return $this->original->connection($this->name);
}
public function getDefaultConnection()
{
return $this->name;
}
}
In test use it like this
create a method called create() inside tests/TestCase.php
protected function create($attributes = [], $model = '', $route = '')
{
$this->withoutExceptionHandling();
$original = $model::getConnectionResolver();
$model::setConnectionResolver(new ConnectionResolver($original, 'testing'));
$response = $this->postJson($route, $attributes)->assertSuccessful();
$model = new $model;
$this->assertDatabaseHas('testing_db.'.$model->getTable(), $attributes);
$model::setConnectionResolver($original);
return $response;
}
and in actual test you can simply do this:
/** #test */
public function user_can_create_model(): void
{
$attributes = [
'name' => 'Test Name',
'title' => 'Test Title',
'description' => 'Test Description',
];
$model = Model::class;
$route = 'model_store_route';
$this->create($attributes, $model, $route);
}
Note: that test method can have only one line when using setUp() method and $this-> notation
And that's it. What this does is forcing the custom connection name (which should be written inside config/database.php) and the model during that call will work with that connection no matter what you specify inside the model, therefore it will store the data into DB which you have specified in $model::setConnectionResolver(new ConnectionResolver($original, 'HERE'));
This is tested for Laravel 8 & 9 and Super Simple.
Here is an example of switching the connection while testing.
In your model ->
class YourModel extends Model {
protected $connection = 'remote';
public function __construct(array $attributes = [])
{
if(config('app.env') === 'testing') {
$this->connection = 'sqlite';
}
parent::__construct($attributes);
}
}
In the Eloquent Model you have the following method.
/**
* Set the connection associated with the model.
*
* #param string|null $name
* #return $this
*/
public function setConnection($name)
{
$this->connection = $name;
return $this;
}
So you can just do
$user = new User();
$user->setConnection('connectionName')
One option would be to create a new environment file just for testing, that way you can overwrite the connection credentials only for your tests and you would not have to touch your models:
tests/CreatesApplication.php
public function createApplication()
{
$app = require __DIR__ . '/../bootstrap/app.php';
$app->loadEnvironmentFrom('.env.testing'); // add this
$app->make(Kernel::class)->bootstrap();
return $app;
}
Copy your .env file to .env.testing and change your database credentials for the connection global_connection to your test database credentials.
I am not sure how you configured your connection but it probably looks something like the following.
database.php
'global_connection' => [
'database' => env('DB_GLOBAL_DATABASE', ''),
'username' => env('DB_GLOBAL_USERNAME', ''),
'password' => env('DB_GLOBAL_PASSWORD', ''),
],
.env.testing:
DB_GLOBAL_DATABASE=database
DB_GLOBAL_USERNAME=username
DB_GLOBAL_PASSWORD=secret
Now you can use the global_connection connection but it will use your test database.
Additionally you could then remove all environment values from the phpunit.xml file and move them into the .env.testing file so you have all environment values for your tests in one place.
If you don't want to create a new environment file you could of course just update the values in your phpunit.xml file:
<php>
<server name="DB_GLOBAL_DATABASE" value="database"/>
<server name="DB_GLOBAL_USERNAME" value="username"/>
<server name="DB_GLOBAL_PASSWORD" value="password"/>
</php>
The most "magical" thing I suggest you could do is focus exclusively on the test and try to not modify the model at all:
/** #test */
public function user_can_create_some_model(): void
{
config([ "database.connections.global_connection" => [
'driver' => 'mysql', 'host' => x // basically override everything that is in config/database.php
]);
$attributes = [
'name' => 'Some Name',
'title' => 'Some Title',
];
$response = $this->postJson($this->route, $attributes)->assertSuccessful();
}
Hopefully when the configuration needs to be read the new one will be used.
If your global_connection configuration is read from the .env file you can also override the env variables in your test runner configuration (e.g. phpunit.xml)

InvalidArgumentException: Action Facade\Ignition\Http\Controllers\ExecuteSolutionController not defined

I'm trying to do the Dusk testing.
ViewAnotherUsersTweetsTest.php
/**
* #test
*/
public function can_view_another_users_tweets()
{
$user = factory(User::class)->create(['username' => 'johndoe']);
// make() stores in memory
$tweet = factory(Tweet::class)->make([
'body' => 'My first tweet'
]);
$user->tweets()->save($tweet);
$this->browse(function ($browser) {
$browser->visit('/johndoe')
->dump();
});
}
Route in web.php : Route::get('{username}', 'UserController#show');
UserController.php
class UserController extends Controller
{
public function show(string $username) {
$user = User::findByUsername($username); // ExecuteSolutionController not defined.
dd($user);
}
}
in App\User.php model file
public static function findByUsername($username) {
return self::where('username', $username)->first();
}
When I execute the test with command php artisan dusk --filter can_view_another_users_tweets, it fails and shows following error message.
(1/1) InvalidArgumentException Action Facade\Ignition\Http\Controllers\ExecuteSolutionController not defined.
What could be the reason and how can I fix it?

InvalidArgumentException: Unable to locate factory with name [default] - laravel, faker, phpunit

Since I am developing package, so I put my factories to custom path like this:
-- app
-- packages
-----mockizart
-------blog
---------database
--------------factories
----------------- PageModelFactory.php
---------src
this is how i load factory in my service provider (I already make sure the path is correct by clicking it on phpstorm):
function boot()
{
Factory::construct($this->app->make(\Faker\Generator::class), __DIR__."/../database/factories");
}
this is my page model factory (I already made sure this file was really loaded):
<?php
/** #var \Illuminate\Database\Eloquent\Factory $factory */
use Mockizart\Blog\Dodols\PageModel;
use Faker\Generator as Faker;
$factory->define(PageModel::class, function (Faker $faker) {
return [
'name' => "retretre",
'slug' => "retretret",
'type' => 0,
'category' => 0,
'tags' => "",
'content' => "",
];
});
and this is my script test :
use Mockizart\Blog\Dodols\PageModel;
.....
.....
/** #test */
public function edit_page()
{
dd(PageModel::find(1)); <-- this return was NULL so I think my class and namespace does exist.
factory(PageModel::class)->make(); <-- this cause error "unable to locate factory......"
$response = $this->get('/blog/page/edit/15');
$response->assertStatus(200);
}
so if you are using orchestra\Testbench, the correct way to load custom factories is in the setUp() method of your Test class or TestCase NOT in your Service Provider.
the code would be like this:
class TestCase extends \Orchestra\Testbench\TestCase
{
public function setUp(): void
{
parent::setUp();
// additional setup
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
$this->withFactories(__DIR__.'/../database/factories');
}
protected function getPackageProviders($app)
{
return BlogServiceProvider::class;
}
}

How to test custom validation rule in Laravel 5.8

i would like to write a test for my custom rules.
my example looks like this.
I'm check if the user gives the correct current password.
my custom rule:
public function passes($attribute, $value)
{
return Hash::check($value, auth()->user()->password);
}
public function message()
{
return 'Your current password is incorrect.';
}
and the test for this rule:
class CurrentPasswordTest extends TestCase
{
use WithFaker, RefreshDatabase;
/** #test */
public function current_password_must_be_valid()
{
$rule = new CurrentPassword();
$user = factory(User::class)->create(['password' => '1234']);
$this->assertTrue($rule->passes('current_password','1234'), $user->password);
}
}
but i'm getting an error:
Tests\Unit\CurrentPasswordTest::current_password_must_be_valid
ErrorException: Trying to get property 'password' of non-object
what i'm doing wrong in this example ?
You need to log in your user before you can use your passes() method - otherwise auth()->user() will be null. You can do that using the be($user) method like this:
/** #test */
public function current_password_must_be_valid()
{
$rule = new CurrentPassword();
$user = factory(User::class)->create(['password' => '1234']);
$this->be($user);
$this->assertTrue($rule->passes('current_password','1234'), $user->password);
}
It would also be advisable to guard against null values in your passes() method to prevent errors. If there is no user logged in, it should probably just return false.

Laravel/PHPUnit: Class not being mocked when tests are run

I'm testing a class that calls a custom service and want to mock out the custom service.
The error is:
App\Jobs\CustomApiTest::getrandomInfo
Error: Call to a member function toArray() on null
This is because in getrandomInfo() there is a database call to fetch an ID and the test database is currently returning null because there is no entry, but the test should never even go that far because I am mocking out the getData function.
Machine Config:
Laravel 5.2
PHPUnit 4.8
I can not update my configuration.
MainClass.php
namespace App\Jobs;
use App\Services\CustomApi;
class MainClass
{
public function handle()
{
try {
$date = Carbon::yesterday();
$data = (new CustomApi($date))->getData();
} catch (Exception $e) {
Log::error("Error, {$e->getMessage()}");
}
}
}
MainClassTest.php
nameSpace App\Jobs;
use App\Services\CustomApi;
class MainClassTest extends \TestCase
{
/** #test */
public function handleGetsData()
{
$data = json_encode([
'randomInfo' => '',
'moreInfo' => ''
]);
$customApiMock = $this->getMockBuilder(App\Services\CustomApi::class)
->disableOriginalConstructor()
->setMethods(['getData'])
->getMock('CustomApi', ['getData']);
$customApiMock->expects($this->once())
->method('getData')
->will($this->returnValue($data));
$this->app->instance(App\Services\CustomApi::class, $customApiMock);
(new MainClass())->handle();
}
}
CustomApi Snippet
namespace App\Services;
class CustomApi
{
/**
* #var Carbon
*/
private $date;
public function __construct(Carbon $date)
{
$this->date = $date;
}
public function getData() : string
{
return json_encode([
'randomInfo' => $this->getrandomInfo(),
'moreInfo' => $this->getmoreInfo()
]);
}
}
I have tried many variations of the above code including:
Not using `disableOriginalConstructor()` when creating $externalApiMock.
Not providing parameters to `getMock()` when creating $externalApiMock.
Using `bind(App\Services\CustomApi::class, $customApiMock)` instead of instance(App\Services\CustomApi::class, $customApiMock) for the app.
Using willReturn($data)`` instead `will($this->returnValue($data))`.
I ended up creating a Service Provider and registering it in the app.php file. It seemed like the application was not saving the instance in the containers but works when it is bound to the service.

Resources