PHPUnit功能测试中的重写类

时间:2019-10-14 21:15:51

标签: laravel phpunit phpunit-testing

我正在尝试测试一个自定义的Artisan命令,该命令可以执行多项操作,然后最后执行csv导入。我在工匠命令中手动new CsvDirectDatabaseImporter实例化该对象。这将运行称为import()的方法,该方法使用SQLite不支持的LOAD DATA LOCAL INFILE从csv导入数据库。因为我想让测试在内存中运行,所以我想覆盖CsvDirectDatabaseImporter类上的import方法(或模拟/存根不知道正确的术语是什么),以便在导入调用期间不执行任何操作。这样,我的其余测试将可以正常工作(我现在知道我没有测试实际的导入),我将如何解决这个问题:

这是我的工匠类的简化版本:

<?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类

<?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()方法。

1 个答案:

答案 0 :(得分:1)

因此,我尝试将我认为您需要的内容复制到一个简单的示例中

假设我们有此命令

<?php

namespace App\Console\Commands;

use Exception;
use Illuminate\Console\Command;

class Foo extends Command
{
    protected $signature = 'foo';

    public function __construct()
    {
        parent::__construct();
    }
    public function handle()
    {
        if ($this->import()) {
            $this->info('Success');
        } else {
            $this->error('Failed');
        }
    }

    public function import()
    {
        throw new Exception('An exception that should not be thrown');
    }
}

import方法会引发异常,但这是模拟它以返回true的方法

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Console\Commands\Foo;

class FooCommandTest extends TestCase
{
    public function testExample()
    {
        $mock = $this->getMockBuilder(Foo::class)->setMethods(['import'])->getMock();
        $mock->method('import')->willReturn(true);
        $this->app->instance('App\Console\Commands\Foo', $mock);
        $this->artisan('foo')
            ->expectsOutput('Success')
            ->assertExitCode(0);
    }
}

此测试通过了两次成功的断言,因此您可以调整命令代码以使用专用的导入方法

希望这会有所帮助