I have a testsuite for a laravel project. For now I use disk file for my tests.
Question: Is there a way I can use memory database and migrate database only once before starting the test suite?
To be clear: I want to migrate the database only one time. I will have more thand 100 tests and I want only one migration to be executed
In your TestCase.php (or in any other test if you want use memory database only in that test) you can override setUp() method
public function setUp()
{
parent::setUp();
$this->app['config']->set('database.default', 'testing');
$this->app['config']->set('database.connections.testing', [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => ''
]);
}
You can use Illuminate\Foundation\Testing\DatabaseMigrations in test class body as well. In this case, php artisan migrate:refresh will be called before each test
<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
// ...
// test cases
// ...
}
Related
When trying a test that came with Laravel and Jetstream/Livewire libraries, I get an undefined array key "fingerprint" error message
Undefined array key "fingerprint"
at vendor/livewire/livewire/src/Testing/TestableLivewire.php:181
public function pretendWereSendingAComponentUpdateRequest($message, $payload)
{
$result = $this->callEndpoint('POST', '/livewire/message/'.$this->componentName, [
'fingerprint' => $this->payload['fingerprint'],
'serverMemo' => $this->payload['serverMemo'],
'updates' => [['type' => $message, 'payload' => $payload]],
]);
This happens for any out of the box feature tests that ship with Laravel9 with Jetstream when used against my project.
Here is one example that fails at the Livewire::test.... line.
The user is created and authenticating without issue and confirmed in other phpunit tests.
class BrowserSessionsTest extends TestCase
{
use RefreshDatabase;
public function test_other_browser_sessions_can_be_logged_out(): void
{
$this->actingAs($user = User::factory()->create());
Livewire::test(LogoutOtherBrowserSessionsForm::class)
->set('password', $user->password)
->call('logoutOtherBrowserSessions')
->assertSuccessful();
}
}
I stood up a fresh Laravel 9 project which works and began inserting various areas from my project into the fresh project as a way of hopefully identifying the issue. Session parameters, events, migrations, factories, models, were not the issue as it continued to work in the fresh project.
One thing I noticed is that the generic routes are not accepted in my project within he test cases. I have to insert 'https://realtor.host' in front of every test route (e.g. $response = $this->get('https://realtor.host/register');
I was curious if it was not evaluating the livewire route and I tried to add my domain into the vendor's livewire component in which the test still failed and that did not cause it to work.
Any ideas on where else I can look?
I would like to implement a debug output in our test environments in which I would like to output service requests that the application sends.
For this I wanted to use the symfony/twig function dump(), because here the output is wonderfully formatted for all types of variables and also offers the option of opening and closing the structure.
Pseudo-code would be something like this
{% if debugEnabled %}
{{dump (debugInfos)}}
{% endif %}
Unfortunately, "dump" is part of the Symfony DebugBundle, which for good reasons is not loaded in Prod environments and which should stay that way:
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
In the Symfony documentation says:
By design, the dump() function is only available in the dev and test
environments, to avoid leaking sensitive information in production. In
fact, trying to use the dump() function in the prod environment will
result in a PHP error.
I don't want to use dump() in production environments at all, but only locally to output our service requests.
However, I cannot implement a code like above because an error always occurs in production (undefined function dump()) of course, since dump() is not loaded at all.
You can create your own function which tests if the extension is loaded.
As you can see in the source the function twig_var_dump is registered, so you can test if the function exists or not to determine if the debug extension was loaded or not.
Should be fine to register your own function as dump as long as your extension gets loaded after the debug extension
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Twig\Environment;
class AppExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('dump', [$this, 'dump'] , ['needs_context' => true, 'needs_environment' => true, ]),
];
}
public function dump(Environment $env, $context, ...$vars)
{
if (!function_exists('\twig_var_dump')) return;
return \twig_var_dump($env, $context, ...$vars);
}
}
sidenote: Not tested this but should work
Create your own extension that's only loaded in production.
Create a folder (for example Twig) in your src directory.
Make that folder excluded from autowiring if you use the default:
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
- '../src/Twig/' # This is the directory excluded from autowiring
Then create a config/services_prod.yaml file with this content:
services:
App\Twig\FakeDebugExtension: # replace with your class name
tags:
- twig.extension # not needed if you use autoconfiguration
Then create a class with this content:
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
final class FakeDebugExtension extends AbstractExtension
{
public function getFunctions(): array
{
return [
new TwigFunction('dump', [$this, 'fakeDump']),
];
}
public function fakeDump(...$arguments): void
{
}
}
i'm having a bit of a problem with my PHPUnit integration tests, i have a method which handles a form upload for a video file as well as a preview image for that video.
public function store($request)
{
/** #var Video $resource */
$resource = new $this->model;
// Create a new Content before creating the related Photo
$contentRepo = new ContentRepository();
$content = $contentRepo->store($request);
if($content->isValid()) {
$resource->content_id = $content->id;
$directory = 'frontend/videos/assets/'.date("Y").'/'.date('m').'/'.time();
\File::makeDirectory($directory, 0755, true);
$request->video->move($directory.'/', $request->video->getClientOriginalName());
$resource->video = '/'.$directory.'/'.$request->video->getClientOriginalName();
$request->preview_image->move($directory.'/', $request->preview_image->getClientOriginalName());
$resource->preview_image = '/'.$directory.'/'.$request->preview_image->getClientOriginalName();
$resource->highlighted = intval($request->input('highlighted') == 'on');
$resource->save();
return $resource;
}
else {
return $content;
}
}
The important part to keep is the $request->video->move() call which i probably need to replace in order to use Virtual Filesystem.
and then the test
public function testVideoUpload(){
File::put(__DIR__.'/frontend/videos/assets/image.mp4', 'test');
$file = new UploadedFile(__DIR__.'/frontend/videos/assets/image.mp4', 'foofile.mp4', 'video/mp4', 100023, null, $test=true);
File::put(__DIR__.'/frontend/images/assets/image.jpg', 'test');
$preview = new UploadedFile(__DIR__.'/frontend/images/assets/image.jpg', 'foofile.jpg', 'image/jpeg', 100023, null, $test=true);
$this->post('/admin/videos', [
'title' => 'My Video #12',
'description' => 'This is a description',
'actors' => [$this->actor->id, $this->actor2->id],
'scenes' => [$this->scene->id, $this->scene2->id],
'payment_methods' => [$this->paymentMethod->id],
'video' => $file,
'preview_image' => $preview
])->seeInDatabase('contents', [
'title' => 'My Video #12',
'description' => 'This is a description'
]);
}
As you can see, i need to create a dummy file in some local directory and then use that in the HTTP request to the form's endpoint, then after that, that file would be moved and i need to delete the created folder and the new moved file... it's an authentic mess.
As such i want to use Virtual Filesystem instead, but i have no idea how to set it up in this particular case, i've already downloaded a package and set it up, but the questions are, first, which package have you used/recommend and how would you tweak the class and the test to support the Virtual Filesystem? Would i need to switch over to using the Storage facade instead of the $request->video->move() call? If so how would that be done exactly?
Thank you in advance for your help
I couldn't figure out the VFS system, however i do have somewhat of an alternative that's still kinda messy but gets the job done.
Basically i set up two methods on my PHPUnit base class to setup and teardown the temp folders i need on any test that requires them, because i'm using Database Transactions the files get deleted on every test run and i need to create new dummy files every time i run the test.
So i have two methods setupTempDirectories and teardownTempDirectories which i will call at the beginning and at the end of each test that requires those temporary directories.
I put my temp files in the Storage directory because sometimes i run my tests individually through PHPStorm and the __DIR__ command gets messed up and points to different directories when i do that, i also tried __FILE__ with the same result, so i just resorted to using Laravel's storage_path instead and that works fine.
Then that leaves the problem of my concrete class which tries to move files around and create directories in the public folder for them... so in order to fix that i changed the code to use the Storage facade, then i Mock the Storage facade in my tests
So in my concrete class
$directory = 'frontend/videos/assets/'.date("Y").'/'.date('m').'/'.time();
Storage::makeDirectory($directory, 0755, true);
Storage::move($request->video, $directory . '/' . $request->video->getClientOriginalName());
$resource->video = '/'.$directory.'/'.$request->video->getClientOriginalName();
Storage::move($request->preview_image, $directory . '/' . $request->preview_image->getClientOriginalName());
$resource->preview_image = '/'.$directory.'/'.$request->preview_image->getClientOriginalName();
And then in my test i mock both the makeDirectory and the move methods like such
// Override the Storage facade with a Mock version so that we don't actually try to move files around...
Storage::shouldReceive('makeDirectory')->once()->andReturn(true);
Storage::shouldReceive('move')->twice()->andReturn(true);
That makes my tests work and does not actually leave files behind after it's done...i hope someone has a better solution but for the time being this is what i came up with.
I was actually trying to use VFS but it never worked out... i keep getting errors that the original file in the storage directory is not found even though it's right there...
I'm not even sure the Storage facade was using VFS in the background to begin with even though it should...
I try to set up a symfony project with the microcontroller trait. But instead of use a config.yml I want to use a config.php file.
return [
'framework' => [
'secret' => 'secret_'
]
];
What is the best practice to achieve this?
when using microkernel trait, you can use the configureContainer method in your front controller (app.php) to load configuration directly from an array, like this:
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
{
// PHP equivalent of config.yml
$c->loadFromExtension('framework', array(
'secret' => 'S0ME_SECRET'
));
}
docs here
You should use the container to set the parameters like
$container->setParameter('framework.secret', 'secret_');
as explained in the Symfony Docs
I have a Laravel 4 application with sqlite db configured for testing.
I am working in a workbench package
I have a problem testing my models in a PHPUnit Test, because i defined some unique properties on my model. I run Artisan::call('migrate', array('--bench' => 'vendor/webshop')); from my Basic Test Class from which i extend other Tests.
I think this runs the database migrations, but in my opinion it does not delete the models in the database.
Because if i do
public function setUp() {
parent::setUp();
$this->order = Order::create(array(
"uniquekey" = "123"
));
}
I get an error saying, can not insert model because of violation of unique key rule.
How should i clean the database before every test?
You should define environment for testing purposes.
Actually Laravel does have one for you - notice testing folder inside your app/config.
Create app/config/testing/database.php (or modify if exists) and place this:
return array(
'default' => 'sqlite',
'connections' => array(
'sqlite' => array(
'driver' => 'sqlite',
'database' => ':memory:', // this will do the trick ;)
'prefix' => '',
),
),
);
When you run tests, Laravel sets it's environment to 'testing' automaticaly - no need to play with this.
SQLite in-memory DB is memory stored withing RAM, thus it's fast - and yes, tests start with empty database as well.