PHPUnit feature tests pass locally but not in Github Actions - laravel

I have a set of tests that pass locally, but once I push to Github, the tests fail. The Github Action seems like a black box to me, and I'm unsure of even how to troubleshoot this. I'm using in-memory sqlite both locally and in my github action workflow.
I'm inserting some data for my tests, and at the beginning of these seeder classes, I'm temporarily disabling foreign keys to allow me to truncate the tables:
Schema::disableForeignKeyConstraints();
QuickbooksTransaction::truncate();
Schema::enableForeignKeyConstraints();
Here's the start of my test class that's producing the errors:
class FindMatchesTest extends TestCase
{
use RefreshDatabase, WithFaker;
protected $user;
protected $client;
protected $location;
protected $statement;
public function setup(): void
{
parent::setUp();
$this->setupFaker();
$this->seed();
$this->user = User::factory()->create();
$this->client = Client::factory()->create();
$this->location = Location::factory()->create();
$this->statement = Statement::factory()->create();
$this->seed(StatementTransactionSeeder::class); //truncates and populates a table
$this->seed(TransactionSeeder::class); //truncates and populates a table
$action = new FindMatches($this->statement, true);
$action->handle();
}
this is the first of 3 tests that are failing. It doesn't matter which assertion I put first, they all fail:
public function test_right_matching_invoice_nbr_produces_match()
{
$row = StatementTransaction::where('invoice_nbr', '999999654321')->first();
$qbRow = Transaction::where('invoice_nbr', '888888654321')->first();
$this->assertNotEmpty($row->transaction_id);
$this->assertEquals(1, $row->has_partial_match);
$this->assertEquals(0, $row->has_match);
$this->assertEquals(0, $row->has_identified_match);
$this->assertEquals($row->transaction_id, $qbRow->id);
$this->assertGreaterThanOrEqual(Config::get('match_threshold'), $row->invoice_nbr_match_confidence);
}
If I leave the order that's above, I get Failed asserting that 7 matches expected 8.
If I switch the order up and put the next row first, I get Failed asserting that 1 matches expected 0.
It's almost like it's seeeing different data than what I'm seeing locally, but that doesn't seem possible if it's running it the same way as it is locally.
Could there be a difference in how PHPUnit would run locally and how it would run in Github actions? Is there a way to see the data it's seeing when it runs so I can begin to troubleshoot this?
Here is my Github Action config file:
name: LaravelTest
on:
push:
branches: [ test ]
jobs:
laravel_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#main
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress
- name: Generate key
run: php artisan key:generate
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Create Database
run: |
mkdir -p database
touch database/database.sqlite
- name: Compile assets
run: |
npm install
npm run production
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
CACHE_DRIVER: array
SESSION_DRIVER: array
QUEUE_DRIVER: sync
run: vendor/bin/phpunit
forge_deploy:
runs-on: ubuntu-latest
needs: laravel_tests
steps:
- name: Make Get Request
uses: satak/webrequest-action#master
with:
url: ${{ secrets.TEST_DEPLOY_URL }}
method: GET
EDIT: adding the seeder classes. Both classes are close to identical, with just a bunch of insert statements to create the dummy records for testing.
class TransactionSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Schema::disableForeignKeyConstraints();
Transaction::truncate();
Schema::enableForeignKeyConstraints();
$id = Statement::first()->id;
$inv = Helper::cleanInvoiceNumber('888888654321');
$date = Carbon::now()->startOfMonth()->format('Y-m-d');
DB::table('transactions')->insert([
'statement_id' => $id,
'transaction_type_id' => 1,
'transaction_date' => $date,
'invoice_nbr' => '888888654321',
'invoice_nbr_clean' => $inv,
'original_amount' => '16100',
'balance' => '16100',
'row_hash' => Helper::makeHash([
$id,
1,
$inv,
$date,
'16100'
])
]);
//...more inserts like above...
}
class StatementTransactionSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Schema::disableForeignKeyConstraints();
StatementTransaction::truncate();
Schema::enableForeignKeyConstraints();
$id = Statement::first()->id;
$inv = Helper::cleanInvoiceNumber('999999654321');
$date = Carbon::now()->startOfMonth()->format('Y-m-d');
DB::table('statement_transactions')->insert([
'statement_id' => $id,
'transaction_type_id' => 1,
'transaction_date' => $date,
'invoice_nbr' => '999999654321',
'invoice_nbr_clean' => $inv,
'po_nbr' => 'Right 6 match',
'original_amount' => '16100',
'balance' => '16100',
'row_hash' => Helper::makeHash([
$id,
1,
$inv,
$date,
'16100'
])
]);
//...more inserts like above...
}
Clarification requested in comment:
When I say switch the order, I just mean that the first assertion always fails in this test, regardless of the order of the assert statements. So this:
$this->assertNotEmpty($row->transaction_id);
$this->assertEquals(1, $row->has_partial_match);
or this:
$this->assertEquals(1, $row->has_partial_match);
$this->assertNotEmpty($row->transaction_id);
will both fail on the first assert statement it hits. This, combined with the error message it spits out, leads me to believe that the data isn't the same on the server as it is locally for some reason.
UPDATE
Based on feedback in the comments, I updated the line to get the statement with this: $id = Statement::orderBy('id', 'desc')->first()->id;
Now all the tests in this class are failing with the message: You requested 1 items, but there are only 0 items available..
I have no idea what this means. Currently wading through google results.
UPDATE 2
Ok that was wrong. I think I incorrectly removed a line of code for a factory that wasn't needed in the test, but was a dependency for another factory.
I'm back at square one.
UPDATE 3
In an effort to not spend my entire life on this, I'm going back to the drawing board and eliminating the seeder classes and just inserting the records I need for the specific test inside the test itself. Hoping this resolves the issue so I don't have to jump off a cliff in despair.

Related

Laravel: Check if new items added to db table and schedule a job to email user

I want to trigger an email when new rows are added to a table in my Laravel application. However I want to add a buffer of sorts, so if 5 rows are added in quick succession then only 1 email is sent.
The method I've chosen is to schedule a check every 15 minutes and see if there are new rows added. If there are then I will queue an email.
Currently I'm getting an error on the schedule. I'll run through my code below:
In Kernel.php where we setup schedules I have:
$schedule->job(new ProcessActivity)
->everyFifteenMinutes()
->when(function () {
return \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(15), Carbon::now()))->exists();
})
->onSuccess(function () {
Log::debug(
'Success'
);
})
->onFailure(function () {
Log::debug(
'Fail'
);
});
Which I use to trigger the Job found in: App\Jobs\ProcessActivity.php :
public function __construct()
{
$this->jobs = \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(15), Carbon::now()))->get();
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
Log::debug('Activity Job Run', ['jobs' => $this->jobs]);
$this->jobs->each(function ($item, $key) {
Log::debug('loop');
// get project
$project = $item->project;
// get project email
$user_id = $project->user_id;
$email = \App\User::find($user_id)->email;
// get project UUID
$projectUuid = $project->public_id;
// emails
$subscriberEmails = \App\ProjectSubscription::where('project_id', $project->id)->get();
// create activity email
Notification::route('mail', $subscriberEmails)->notify(new Activity($project, $projectUuid));
});
return true;
}
I've posted my full code above which also shows a relationship between my JobItems and Project models. I won't elaborate on that as I've commented in the code.
The problem
When I add a new row to my JobItem table I can see the job is scheduled and processed (using Laravel Telescope to inspect this).
However, I can also see in my log that for each job I get two log messages:
First: 'Fail' and then 'Activity Job Run'
My email is not sent and I'm uncertain how to determine why this is failing.
So it seems that onFailure is being triggered and there is a problem with my ProcessActivity.
Any clues on where I am going wrong and how to determine the error would be much appreciated.
I have a fix, but first, here are some things I learnt that hampered my progress:
I was using this artisan command to process my scheduled jobs:
php artisan queue:work
The problem with developing while using that command is that if there are code changes then those changes are not recognised.
So you can either Command+C to return to the console and use this every time there is a code change:
php artisan queue:restart
php artisan queue:work
Or you can just use this and it will allow code changes:
php artisan queue:listen
As you can imagine without knowing this you will have a slow debugging process!
As a result of this and adding an exception to my Job I made some progress. I'll paste in the code below to compare against the original code:
public function __construct()
{
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
try {
$jobs = \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(20), Carbon::now()))->get();
Log::debug('Activity Job', ['jobs' => $jobs]);
// collection start
$collection = collect();
// loop jobs to get emails
foreach ($jobs as $key => $value) {
// get project UUID
$project = $value->project;
$projectUuid = $project->public_id;
// get emails subscribed to projects
$subscriberEmails = \App\ProjectSubscription::where('project_id', $project->id)->get();
// merge into a single collection via the loop
if ($key != 0) {
$merge = $collection->merge($subscriberEmails);
$collection = collect($merge);
} else {
$collection = $subscriberEmails;
}
// Log::debug('emails_project in loop', ['emails' => $subscriberEmails]);
};
// clean object with uniques only
$subscriberEmailsCleaned = $collection->unique();
// debug
Log::debug('Project Emails to Notify', ['emails' => $subscriberEmailsCleaned]);
// create activity email
Notification::route('mail', $subscriberEmailsCleaned)->notify(new Activity($project, $projectUuid));
} catch (\Exception $e) {
\Log::info($e->getMessage());
}
}
First thing to note, is that as __construct() is run initially and is serialised. Then the handle method is called when the job is processed. So I had to move my eloquent query into the handle method.
I also used a foreach rather than .each to loop through and create a new collection of emails. Perhaps there is a more elegant way, but I needed to create a collection of emails and this way allowed me to move the variables in the loop outside to be used in the method.
You can see me merge these at the bottom of the loop.
I have also added a few Log:: items which is useful for debugging.
Not fixed 100%
With this code I can now auto schedule an email every x minutes when new items are added. However, I am still getting the log Fail from the onFailure()from my Kernal.php file:
->onFailure(function () {
Log::debug(
'Fail'
);
I am still confused as to what that indicates and how I can determine more information about how this has failed and what that means. However, it does work so I will cautiously move forward (with one eye open on the comments, in case someone has an idea that can help!)

i must press enter on running tests using phpunit in laravel

i must press enter on running tests using phpunit in Laravel 5.7.
On every test i get following Message:
1) Tests\Feature\DepartmentsTest::a_admin_can_create_a_department
Mockery\Exception\BadMethodCallException: Received
Mockery_1_Illuminate_Console_OutputStyle::askQuestion(), but no
expectations were specified
by setting following to false, the error disappear:
public $mockConsoleOutput = false;
After that the window hangs on running the test suite and i need to press enter to run the tests.
How can i fix that?
I´am using Windows 10 + PHPUnit 7.5.1 and Laravel 5.7.19
Thanks in advance!
/** #test */
public function a_admin_can_create_a_department()
{
// $this->withoutExceptionHandling();
$attributes = [
'description' => 'Service',
'accessible_by_depart' => true
];
$this->post('/tools/api/storeDepartment', $attributes);
$this->assertDatabaseHas('departments', $attributes);
}
This fixed the problem for me
https://stackoverflow.com/a/48303288/2171254
After doing that, I didn't need the line public $mockConsoleOutput = false;
Greetings
So now I finally found the solution.
On my migration from Laravel 5.1 to Laravel 5.2 (a long time ago) i forgot to add the following lines to the config/app.php File:
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
Now everything works fine.
Greetings Daniel

Unit testing on laravel file downloading location

I am new to Laravel. I am writing unit testing on laravel for downloading a csv file. When I run the test case, I get assertResponseStatus as 200 and I need to open the created csv file and I am unable to find the location of downloaded file. How can I find the downloaded file.
This is the controller fuction
public function generateCsv(){
$list = $this->csvRepo->getDetails();
$heading = array(
'heading 1',
'heading 2',
'heading 3'
);
$this->csvRepo->generateCsv($heading,'csv',$list);
}
I need to know the location of downloaded file when run the test case
Assuming you are using the latest version of Laravel / PHP Unit you are able to use the following:
class ExampleFileDownload extends TestCase
{
public function fileDownloads()
{
Storage::fake('file');
// assuming we wanted to test like this:
$response = $this->json('POST', '/test', [
'file' => UploadedFile::fake()->image('testing.jpg')
]);
// Assert the file was stored – I believe this is the line you are looking for
Storage::disk('file')->assertExists('testing.jpg');
// Assert a file does not exist...
Storage::disk('file')->assertMissing('missing.jpg');
}
}
Let me know how you get on :)

How to Run Laravel Database Seeder from PHPUnit Test setUp?

I am trying to recreate the database before each test in some PHPUnit test cases. I am using Laravel 5.3. Here is TestCase:
class CourseTypesTest extends TestCase
{
public function setUp()
{
parent::setUp();
Artisan::call('migrate');
Artisan::call('db:seed', ['--class' => 'TestDatabaseSeeder ', '--database' => 'testing']);
}
/**
* A basic functional test example.
*
* #return void
*/
public function test_list_course_types()
{
$httpRequest = $this->json('GET', '/api/course-types');
$httpRequest->assertResponseOk();
$httpRequest->seeJson();
}
public function tearDown()
{
Artisan::call('migrate:reset');
parent::tearDown();
}
}
Running phpunit fails with error:
$ phpunit PHPUnit 5.7.5 by Sebastian Bergmann and contributors.
E 1 /
1 (100%)
Time: 2.19 seconds, Memory: 12.00MB
There was 1 error:
1) CourseTypesTest::test_list_course_types ReflectionException: Class
TestDatabaseSeeder does not exist
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Container\Container.php:749
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Container\Container.php:644
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Foundation\Application.php:709
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Database\Console\Seeds\SeedCommand.php:74
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Database\Console\Seeds\SeedCommand.php:63
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:2292
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Database\Console\Seeds\SeedCommand.php:64
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Container\Container.php:508
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Console\Command.php:169
D:\www\learn-laravel\my-folder-api\vendor\symfony\console\Command\Command.php:254
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Console\Command.php:155
D:\www\learn-laravel\my-folder-api\vendor\symfony\console\Application.php:821
D:\www\learn-laravel\my-folder-api\vendor\symfony\console\Application.php:187
D:\www\learn-laravel\my-folder-api\vendor\symfony\console\Application.php:118
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Console\Application.php:107
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Foundation\Console\Kernel.php:218
D:\www\learn-laravel\my-folder-api\vendor\laravel\framework\src\Illuminate\Support\Facades\Facade.php:237
D:\www\learn-laravel\my-folder-api\tests\rest\CourseTypesTest.php:17
ERRORS! Tests: 1, Assertions: 0, Errors: 1.
but this class exists:
Since version 5.8 you can do:
// Run the DatabaseSeeder...
$this->seed();
// Run a single seeder...
$this->seed(OrderStatusesTableSeeder::class);
Take a look at the documentation
The DatabaseSeeder can be instantiated on its own, and its call method is public.
All you need to do in your CourseTypesTest class would be
(new DatabaseSeeder())->call(TestDatabaseSeeder::class);
Or you can make use of Laravel's app helper as follow
app(DatabaseSeeder::class)->call(TestDatabaseSeeder::class);
The problem is empty space in your --class argument. If you take close look at array '--class' => 'TestDatabaseSeeder ' there is space in the end ... this is the problem. Change it to '--class' => 'TestDatabaseSeeder' and it should work fine.
You can try this way. You can execute this command when you run your test.

Symfony 1.4: Override doctrine:build-schema command

Is it possible to override a Symfony 1.4 CLI command?
More specifically, I was wondering if it's possible to override this command:
php symfony doctrine:build-schema
What I want to do is to add a new option in the database.yml file for each connection I find in it.
The option I want to add is a package option
So, an hypothetical connection could be:
all:
doctrine:
class: sfDoctrineDatabase
package: myPackageOption
param:
dsn: 'mysql:host=localhost;dbname=my_db_name'
username: db_user
password: db_password
If it would be possible, where can i find the code to override?
I suggest you to use some shell script that pre-generate the databses.yml and then auto-invoque the php symfony doctrine:build-schema. Something like:
build.sh, in project root folder:
#!/bin/bash
cp config/databases_1.yml config/databases.yml
php symfony doctrine:build
then, type ./build.sh (after added execution permissions) in your console.
The copy/replace of multiple databases_xxx.yml it's the easiest example. But you can do any processing you want.
If you don't know about shell scripting, you can do the file modification even with a php script, so your build.sh should looks like:
#!/bin/bash
php pregenerate_databases.php
php symfony doctrine:build
I'm trying to override the task but I can't make it work, but:
You can create your own task that inherits the doctrine task and do your stuff:
in lib/task add sfDoctrineBuildSchemaCustomTask.class.php:
class sfDoctrineBuildSchemaCustomTask extends sfDoctrineBuildSchemaTask
{
/**
* #see sfTask
*/
protected function configure()
{
$this->addOptions(array(
new sfCommandOption('application', null, sfCommandOption::PARAMETER_OPTIONAL, 'The application name', true),
new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'dev'),
));
$this->namespace = 'doctrine';
$this->name = 'build-schema-custom';
$this->briefDescription = 'Creates a schema from an existing database';
$this->detailedDescription = <<<EOF
The [doctrine:build-schema|INFO] task introspects a database to create a schema:
[./symfony doctrine:build-schema|INFO]
The task creates a yml file in [config/doctrine|COMMENT]
EOF;
}
/**
* #see sfTask
*/
protected function execute($arguments = array(), $options = array())
{
// do your stuff before original call
parent::execute($arguments,$options);
// do your stuff after original call
}
}
Then, you can call php symfony doctrine:build-schema-custom, and go!
Or, maybe, you can edit the original task located in lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/task/sfDoctrineBuildSchemaTask.class.php

Resources