Я пытаюсь протестировать пользовательскую команду Artisan, которая выполняет несколько операций, а затем в конце выполняет импорт CSV. Я создаю экземпляр объекта вручную new CsvDirectDatabaseImporter
внутри команды ремесленника. При этом запускается метод с именем import()
, который импортирует из csv в базу данных, используя LOAD DATA LOCAL INFILE
, который не поддерживается SQLite. Так как я хочу, чтобы мои тесты выполнялись в памяти, я хочу переопределить (или mock / stub не уверен, каков правильный термин) метод импорта в классе CsvDirectDatabaseImporter, чтобы он ничего не делал во время вызова импорта. Таким образом, остальные мои тесты будут работать (теперь я знаю, что не проверяю фактический импорт). Как мне обойти это:
Вот упрощенная версия моего класса Artisan:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use App\Services\CsvDirectDatabaseImporter\CsvDirectDatabaseImporter;
use App\Services\CsvDirectDatabaseImporter\MyColumns;
class DataMartImport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'myimport:import
{year : The year of processing} ';
/**
* The console command description.
*
* @var string
*/
protected $description = 'My Import';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$year = $this->argument('year');
// Copy the file to processing location.
File::copy($files[0], $processing_file);
// Import the CSV File.
$csvImporter = new CsvDirectDatabaseImporter($processing_file, 'myTable', new MyColumns());
$csvImporter->import();
}
}
Упрощенная версия теста функций для запуска моей пользовательской команды ремесленника:
<?php
namespace Tests\Feature\Console\DataMart;
use Illuminate\Support\Facades\File;
use Tests\TestCase;
use Illuminate\Support\Facades\Config;
use Mockery as m;
use App\Services\CsvDirectDatabaseImporter\DataMartColumns;
use App\Services\CsvDirectDatabaseImporter\CsvDirectDatabaseImporter;
use Illuminate\Support\Facades\Artisan;
class MyImportTest extends TestCase
{
public function testImportFoldersGetCreatedIfNoDirectory()
{
$year = 2019;
$this->artisan('myimport:import', ['year' => $year]);
// Assertions of items go here unrelated to the actual database import.
}
}
CSVImorter Class
<?php
namespace App\Services\CsvDirectDatabaseImporter;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Symfony\Component\HttpFoundation\File\File as CSV_File;
class CsvDirectDatabaseImporter {
/**
* File to import.
*
* @var \Symfony\Component\HttpFoundation\File\File
*/
private $file;
/**
* Table name.
*
* @var string
*/
private $table;
/**
* Fields terminated by.
*
* @var string
*/
public $fieldsTerminatedBy = '\',\'';
/**
* Enclosed by.
*
* @var string
*/
public $enclosedBy = '\'"\'';
/**
* Lines terminated by.
*
* @var string
*/
public $linesTerminatedBy = '\'\n\'';
/**
* Ignore first row.
*
* @var bool
*/
public $ignoreFirstRow = true;
/**
* Csv Import columns.
*
* @var array
*/
public $columns;
/**
* CsvImporter constructor.
*
* @param string $path
* The full temporary path to the file
*/
public function __construct(string $path, $table, CsvDirectDatabaseImportColumns $columns)
{
$this->file = new CSV_File($path);
$this->table = $table;
$this->columns = $columns->getColumns();
}
/**
* Import method used for saving file and importing it using database query.
*/
public function import()
{
// Normalize line endings
$normalized_file = $this->normalize($this->file);
// Import contents of the file into database
return $this->importFileContents($normalized_file, $this->table, $this->columns);
}
/**
* Convert file line endings to uniform "\r\n" to solve for EOL issues
* Files that are created on different platforms use different EOL characters
* This method will convert all line endings to Unix uniform
*
* @param string $file_path
* @return string $file_path
*/
protected function normalize($file_path)
{
// Load the file into a string.
$string = @file_get_contents($file_path);
if (!$string) {
return $file_path;
}
// Convert all line-endings using regular expression.
$string = preg_replace('~\r\n?~', "\n", $string);
file_put_contents($file_path, $string);
return $file_path;
}
/**
* Import CSV file into Database using LOAD DATA LOCAL INFILE function
*
* NOTE: PDO settings must have attribute PDO::MYSQL_ATTR_LOCAL_INFILE => true
*
* @param string $file_path
* File path.
* @param string $table_name
* Table name.
* @param array $columns
* Array of columns.
*
* @return mixed Will return number of lines imported by the query
*/
private function importFileContents($file_path, $table_name, $columns)
{
$prefix = config('database.connections.mysql.prefix');
$query = '
LOAD DATA LOCAL INFILE \'' . $file_path . '\' INTO TABLE `' . $prefix . $table_name . '`
FIELDS TERMINATED BY ' . $this->fieldsTerminatedBy . '
ENCLOSED BY ' . $this->enclosedBy . '
LINES TERMINATED BY ' . $this->linesTerminatedBy . '
';
if ($this->ignoreFirstRow) {
$query .= ' IGNORE 1 ROWS ';
}
if ($columns) {
$query .= '(' . implode(",\n", array_keys($columns)) . ')';
$query .= "\nSET \n";
$sets = [];
foreach ($columns as $column) {
$sets[] = $column['name'] . ' = ' . $column['set'];
}
$query .= implode(",\n", $sets);
}
return DB::connection()->getPdo()->exec($query);
}
}
CsvDirectDatabaseImportColumns Интерфейс
<?php
namespace App\Services\CsvDirectDatabaseImporter;
interface CsvDirectDatabaseImportColumns
{
/**
* Returns array of columns.
*
* Ex:
* '@user_id' => [
* 'name' => 'user_id',
* 'set' => '@user_id',
* ],
* '@agent_number' => [
* 'name' => 'agent_number',
* 'set' => 'LEFT(@agent_number, 7)',
* ],
*
* The key should be the column name of the csv but add @ in front. The name
* will be the database table. The set will be what it s se
*
* @return array
*/
public function columns();
/**
* Returns columns.
*
* @return array
* Columns.
*/
public function getColumns();
}
То, что я пытался
$mock = $this->createMock(CsvDirectDatabaseImporter::class);
$mock->method('import')->willReturn(true);
$this->app->instance(CsvDirectDatabaseImporter::class, $mock);
$this->artisan('datamart:import', ['year' => $year]);
Но не повезло. Он все еще выполняет обычный метод import ().