Как изменить жестко закодированное соединение Eloquent $ только в тестах phpunit? - PullRequest
4 голосов
/ 21 февраля 2020

У меня есть Eloquent Model, подобная этой:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;


class SomeModel extends Model
{

    protected $connection = 'global_connection';

......................

Проблема в том, что это $ соединение должно быть жестко запрограммировано, потому что у меня мультитенантная веб-платформа, и все арендаторы должны читать из этой базы данных.

Но когда я сейчас в тестах, я иду по маршруту Controller store(), и у меня нет доступа к модели! Я просто делаю это:

public function store()
{
    SomeModel::create($request->validated());

    return response()->json(['msg' => 'Success']);
}

Что прекрасно работает при использовании его в качестве пользователя через браузер ...

Но теперь я хочу как-то заставить эту модель НЕ использовать эту жестко закодированную $ соединение и установите его на Тестирование соединения с базой данных ...

И это мой тест

/** @test */
public function user_can_create_some_model(): void
{
    $attributes = [
        'name' => 'Some Name',
        'title' => 'Some Title',
    ];

    $response = $this->postJson($this->route, $attributes)->assertSuccessful();
}

Есть ли способ достичь этого с некоторыми Laravel волхвами c, может быть :)

Ответы [ 5 ]

1 голос
/ 21 февраля 2020

Потому что вы попросили Laravel волхвов c ... Вот так. Вероятно, чрезмерное и чрезмерное решение.

Давайте сначала создадим интерфейс, единственной целью которого является определение функции, которая возвращает строку подключения.

app / Connection. php

namespace App;

interface Connection
{
    public function getConnection();
}

Тогда давайте создадим конкретную реализацию, которую мы сможем использовать в реальном мире (производство).

app / GlobalConnection. php

namespace App;

class GlobalConnection implements Connection
{
    public function getConnection()
    {
        return 'global-connection';
    }
}

И еще одна реализация, которую мы можем использовать в наших тестах.

app / TestingConnection. php (вы также можете поместить это в свой tests директории, но обязательно измените пространство имен на соответствующее)

namespace App;

class TestingConnection implements Connection
{
    public function getConnection()
    {
        return 'testing-connection';
    }
}

Теперь давайте go вперед и скажем Laravel, какую конкретную реализацию мы хотим использовать по умолчанию. Это можно сделать, перейдя в файл app/Providers/AppServiceProvider.php и добавив этот бит в метод register.

app / Providers / AppServiceProvider. php

namespace App\Providers;

use App\Connection;
use App\GlobalConnection;

// ...
public function register()
{
    // ...
    $this->app->bind(Connection::class, GlobalConnection::class);
    // ...
}

Давайте использовать его в нашей модели.

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();
    }

    // ...
}

Почти там. Теперь в наших тестах мы можем заменить реализацию GlobalConnection реализацией TestingConnection. Вот как.

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
    }
}

Код не проверен, но должен работать. Вы также можете создать фасад для статического доступа к методу, а затем использовать Mockery, чтобы смоделировать вызов метода и вернуть нужную строку соединения во время тестирования.

1 голос
/ 21 февраля 2020

В Eloquent Model у вас есть следующий метод.

/**
 * Set the connection associated with the model.
 *
 * @param  string|null  $name
 * @return $this
 */
public function setConnection($name)
{
    $this->connection = $name;

    return $this;
}

Так что вы можете просто сделать

$user = new User();
$user->setConnection('connectionName')
0 голосов
/ 22 февраля 2020

К сожалению, для меня ни один из этих ответов не сработал из-за моей настройки c БД для мульти-аренды. Мне немного помогли, и это правильное решение для этой проблемы:

Создайте пользовательский класс ConnectionResolver где-нибудь в каталоге tests / в 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;
    }
}

В тесте используйте его следующим образом

создайте метод с именем create () внутри 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;
}

и в реальном тесте вы можете просто сделать 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);
}

Примечание: этот метод теста может иметь только одну строку при использовании метода setUp () и $this-> запись

И все. Это заставляет пользовательское имя соединения (которое должно быть записано в config / database. php), и модель во время этого вызова будет работать с этим соединением независимо от того, что вы указываете внутри модели, поэтому она будет хранить данные в БД, которую вы указали в $model::setConnectionResolver(new ConnectionResolver($original, 'HERE'));

0 голосов
/ 21 февраля 2020

Один из вариантов - создать новый файл среды только для тестирования, чтобы вы могли перезаписывать учетные данные подключения только для ваших тестов и вам не нужно было бы трогать ваши модели:

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;
}

Скопируйте файл .env в .env.testing и измените свои учетные данные базы данных для подключения global_connection к своим учетным данным тестовой базы данных.

Я не уверен, как вы настроили ваше соединение, но, вероятно, оно выглядит примерно так:

база данных. php

'global_connection' => [
    'database' => env('DB_GLOBAL_DATABASE', ''),
    'username' => env('DB_GLOBAL_USERNAME', ''),
    'password' => env('DB_GLOBAL_PASSWORD', ''),
],

.env. тестирование:

DB_GLOBAL_DATABASE=database
DB_GLOBAL_USERNAME=username
DB_GLOBAL_PASSWORD=secret

Теперь вы можете использовать соединение global_connection, но оно будет использовать вашу тестовую базу данных.

Кроме того, вы можете удалить все значения среды из phpunit.xml файл и переместите их в файл .env.testing, чтобы у вас были все значения среды для ваших тестов в одном месте.

Если вы не хотите создавать новый файл среды, вы, конечно, можете просто обновить значения в ваш * 103 2 * файл:

<php>
    <server name="DB_GLOBAL_DATABASE" value="database"/>
    <server name="DB_GLOBAL_USERNAME" value="username"/>
    <server name="DB_GLOBAL_PASSWORD" value="password"/>
</php>
0 голосов
/ 21 февраля 2020

Самая «волшебная» вещь, которую я предлагаю вам сделать, - это сосредоточиться исключительно на тесте и попытаться вообще не изменять модель:

/** @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();
}

Надеюсь, когда нужно будет прочитать новую конфигурацию. будет использоваться.

Если ваша конфигурация global_connection считывается из файла .env, вы также можете переопределить переменные env в конфигурации вашего тестового прогона (например, phpunit. xml)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...