Как правильно настроить этот сценарий тестирования?

У меня есть метод для обработки запроса на кредит, он создает новую запись о кредите на основе входных данных формы и использует ее как зависимость для вызова API.

Я написал тест для этого метода, который включает в себя имитацию вызова API следующим образом:

Http::fake(
    [
        config('services.dummy.url').'/v3/transfers' => Http::response(
            [
                'status' => 'success',
                'message' => 'test message',
                'data' => [
                    'tx_ref' => $ref,
                    'meta' => [
                        'user_id' => $user->id,
                        'loan_id' => $loan->id, //not available in test
                        'loanProvider_id' => $loanProvider->id,
                        'transfer_description' => 'Loan Disbursement',
                    ],
                ],
            ],
            200
        ),
        config('services.dummy.url').'/v3/transactions/'.$ref.'/verify' => Http::response(
            [
                'status' => 'success',
                'message' => 'test message',
                'meta' => [
                    'loan_id' => $loan->id, //not available in test
                ],
            ],
            200
        ),
    ]
);

Дело в том, что мне нужна запись о кредите для вызова API, но она создается не в моем тесте, а в методе.

public function processLoanApplication(Request $request, User $user) {
    $loan = $user->loans()->create([...]);

    // API call
    $transferService->disburseLoan($loan);
}

Это метод disburseLoan:

public function disburseLoan(Loan $loan)
   {
        $data = $this->debitLoanProvider($loan);
        $result = $this->verifyTransfer($data['data']['tx_ref']);
        $this->useRecurringTransferVerificationResult($result);
   }

Он вызывает два API, сфальсифицированных в тесте, и результат используется для некоторой очистки.

В зависимости от результата вызовов API это $this->useRecurringTransferVerificationResult($result); вызывает другие методы для создания записи о переводе, обновления записи о кредите и т. д.

Как правильно настроить этот тест?

Я не понимаю, в чем проблема. Что происходит после запуска disburseLoan? у тебя действительно больше нет строк кода? Что вы утверждаете в своем тесте

matiaslauriti 13.06.2024 12:52

@matiaslauriti, после запуска disburseLoan, среди прочего, обновляется запись о кредите. Тесты утверждают, что некоторые события и уведомления отправляются, а также значения модели базы данных. Проблема в том, что мне нужен идентификатор кредита для Http::fake(), но он доступен только во время выполнения, запись создается, а затем используется в методе disburseLoan.

Adefowowe 13.06.2024 13:25

Окей, теперь я понял. Поэтому я думаю, что тогда вы захотите либо подделать что-то еще внутри disburseLoan, но не эту часть кода, либо вы хотите иметь полный контроль над базой данных, чтобы вы всегда знали, каким будет идентификатор (скажем, у вас нет кредитов вообще, так что вы знаете, что для его создания потребуется id = 1, поэтому вы делаете это при своем поддельном вызове API), имеет ли это смысл? Не могли бы вы добавить немного больше информации о disburseLoan, просто чтобы посмотреть, сможем ли мы подделать или высмеять что-то другое?

matiaslauriti 13.06.2024 18:04

@matiaslauriti, пожалуйста, прочтите мое обновление в ответ на ваш последний комментарий.

Adefowowe 13.06.2024 20:22

Я думаю, что вам, возможно, придется пойти дальше и попытаться подделать что-то другое. Я не могу просить вас начать добавлять все методы в этот вопрос, но попробуйте найти, на какой части можно остановиться и высмеять, вместо того, чтобы высмеивать вызов API.

matiaslauriti 14.06.2024 10:04

Да, я начинаю думать, что просто пошучу над выплатой в этом тесте, исключим тестирование вызова API и вынесем это в отдельный тест.

Adefowowe 14.06.2024 13:08

Это еще одно решение!

matiaslauriti 14.06.2024 14:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
7
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В итоге я получил эту настройку (с помощью

test('that it processes a personal loan request and disbursement was successful', function () {
    // Arrange
    Storage::fake('public');
    $loanProvider = LoanProvider::factory()->create([
        'loan_type' => 'Personal Loan',
        'api_url' => 'dummy.providerurl',
        'api_secret' => 'dummyprovidersecret',
        'endpoints' => ['loanApplication' => '/loan/application']
    ]);
    $bank = Bank::factory()->create();
    $security = Security::factory()->create([
        'asset_category' => 'Equities',
        'market_price' => 100.0,
    ]);
    $user = User::factory()->verified()->identityVerified()->create();
    $user->billing->update([
        'bank_code' => $bank->bank_code,
        'account_number' => '1234567890'
    ]);
    $targetAmount = 15000000;
    $goalHorizon = today()->addYears(10);
    $horizonInYears = (int) (today()->diffInYears($goalHorizon));
    $recommendedSavings = $targetAmount / $horizonInYears;
    $totalProjectedSavings = $recommendedSavings * $horizonInYears;
    $totalProjectedReturns = $totalProjectedSavings * 15;
    $goal = $user->financialGoals()->create([
        'goal_name' => 'fake goal',
        'user_first_name' => $this->authUser->first_name, 
        'user_last_name' => $this->authUser->last_name,
        'target_amount' => $targetAmount,
        'goal_horizon' => $goalHorizon,
        'savings_type' => 'Periodic Savings',
        'recommended_savings' => $recommendedSavings,
        'total_projected_savings' => $totalProjectedSavings,
        'total_projected_returns' => $totalProjectedReturns,
        'renewal_date' => today(),
        'cumulative_cash_savings' => 750000,
        'set_for_implementation' => true,
        'fee_discount' => null,
        'verified_organisation_documents' => false,
        'implementation_status' => 'Active',
        'has_processed_share_transfer_forms' => false,
        'has_processed_dcs_opt_out' => false,
        'clearing_house_number' => null,
        'CSCS_settlement_status' => null,
        'setup_fee_paid_at' => null,
    ]);
    $goal->portfolios()->create([
        'investment_service_provider_id' => InvestmentServiceProvider::factory()->create()->id,
        'security_id' => $security->id,
        'security_asset_category' => $security->asset_category,
        'security_name' => $security->security_name,
        'equity_units' => 9000,
        'equity_unit_cost' => 50.0,
        'equity_total_cost' => 450000.0,
        'equity_market_price' => $security->market_price,
        'equity_market_value' => 900000.0,
    ]);
    $goal->setupSaving()->create([
        'goal_name' => $goal->goal_name,
        'amount' => 50000,
        'start_date' => today(),
        'savings_frequency' => 'Monthly',
    ]);
    $signature = UploadedFile::fake()->image('signature.jpg');
    $user->updateSignature($signature);
    $loanDocs = [
        'loanAgreement' => storage_path('/app/public/PLA'.$user->first_name.'.pdf'),
        'lienDocuments' => storage_path('/app/public/PLL'.$user->first_name.'.pdf')
    ];
    $request = [
        'amount' => 250000.00,
        'bank_code' => $bank->bank_code,
        'account_number' => '1234567890',
        'account_name' => $user->first_name.' '.$user->last_name,
        'terms' => true,
        'loan_type' => 'Personal Loan'
    ];
    $this->partialMock(LoansController::class, function (MockInterface $mock) use ($loanDocs) {
        $mock->shouldReceive('processLoanDocumentation')
            ->withAnyArgs()
            ->once()
            ->andReturn($loanDocs);

        // Allow unexpected methods like getMiddleware
        $mock->makePartial();
    });
    $requiredData = [];
    Http::fake([
        $loanProvider->url.$loanProvider->endpoints['loanApplication'] => Http::response([
            'status' => 'success',
            'message' => 'test message',
            'data' => [
                'status' => 'approved',
                'meta' => [
                    'user_id' => $user->id,
                    'loanProvider_id' => $loanProvider->id,
                ],
            ],
            ], 200),
        config('services.nibss.url').'/v3/transfers' => function ($request) use ($user, $loanProvider,
            &$requiredData) {
            $requestBody = json_decode($request->body(), true);
            $ref = $requestBody['tx_ref'];
            $loanId = $requestBody['meta']['loan_id'];
            $amount = $requestBody['amount'];

            $requiredData['ref'] = $ref;
            $requiredData['loan_id'] = $loanId;
            $requiredData['amount'] = $amount;
            
            return Http::response([
            'status' => 'success',
            'message' => 'test message',
            'data' => [
                'tx_ref' => $ref,
                'amount' => $amount,
                'meta' => [
                    'user_id' => $user->id,
                    'loan_id' => $loanId,
                    'loanProvider_id' => $loanProvider->id,
                    'transfer_description' => 'Loan Disbursement',
                ],
            ],
            ], 200);
        },
        config('services.nibss.url').'/v3/transactions/*' => function () use (&$requiredData, $loanProvider, $user) {
            return Http::response([
                'status' => 'success',
                'message' => 'test message',
                'data' => [
                    'status' => 'successful',
                    'amount' => $requiredData['amount'],
                    'tx_ref' => $requiredData['ref'],
                    'payment_type' => 'card',
                    'created_at' => now(),
                    'ip_address' => null,
                    'processor_response' => 'successful',
                    'card' => [
                        'reusable' => true,
                        'exp_year' => '2026',
                        'exp_month' => '07',
                        'token' => $loanProvider->auth_code,
                    ],
                    'meta' => [
                        'benefit_id' => null,
                        'goal_id' => null,
                        'user_id' => $user->id,
                        'setupBenefit_id' => null,
                        'investment_service_provider_id' => null,
                        'loan_id' => $requiredData['loan_id'],
                        'loanProvider_id' => $loanProvider->id,
                        'reward_id' => null,
                        'providerAuth_id' => null,
                        'rewardProvider' => null,
                        'transfer_description' => 'Loan Disbursement',
                        'bill_type' => null,
                    ],
                ],
            ], 200);
        },
    ]);
    Notification::fake();
    Event::fake();

    // Act
    $response = $this->actingAs($user)->post(route('loans.process-application', $user), $request);

    $loan = Loan::findOrFail($requiredData['loan_id']);

    // Assert
    $recorded = Http::recorded();
    expect($recorded[0][0]->url())->toBe($loanProvider->api_url.$loanProvider->endpoints['loanApplication']);
    expect($recorded[1][0]->url())->toBe(config('services.nibss.url').'/v3/transfers');
    expect($recorded[2][0]->url())->toBe(config('services.nibss.url').'/v3/transactions/'.$requiredData['ref'].'/verify');
    Http::assertSentCount(3);
    $this->assertDatabaseCount('loans', 1);
    $this->assertDatabaseHas('loans', [
        'user_id' => $user->id,
        'loan_provider_id' => $loanProvider->id,
        'principal_amount' => $request['amount'],
        'loan_status' => 'running',
        'loan_start_date' => $loan->fresh()->loan_start_date,
    ]);
    $this->assertDatabaseCount('setup_loan_repayments', 1);
    $this->assertDatabaseHas('setup_loan_repayments', [ "direct_repayment_start_date" => null]);
    $this->assertDatabaseCount('transfers', 1);
    $this->assertDatabaseHas('transfers', [
        'model_id' => $loan->id,
        'amount' => $request['amount'],
        'transfer_status' => 'successful',
        'transfer_description' => 'Loan Disbursement',
    ]);
    Event::assertDispatched(SuccessfulTransfer::class);
    Notification::assertSentTo($loanProvider, SuccessfulLoanDisbursement::class);
    Notification::assertNotSentTo($loanProvider, LoanDocumentation::class);
    Notification::assertSentTo($user, LoanDisbursementNotification::class);
    Event::assertDispatched(PortfolioManagersNotice::class);
    $response->assertStatus(302);
    expect($loan->fresh()->loan_status)->toBe('running');
    expect($loan->fresh()->loan_start_date)->toEqual(today());
    $response->assertSessionHas('flash.banner', 'Your loan application was successful. You will receive funds shortly!');
    $response->assertSessionHas('flash.bannerStyle', 'success');
    $response->assertRedirectToRoute('loans.index');
    $this->followRedirects($response) // works but method isn't documented
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Loans/AllLoans')
    );
});

Не могли бы вы отредактировать ответ и сообщить, где созданы $isp и другая переменная? Нам нужно увидеть полный порядок выполнения, чтобы понять решение.

matiaslauriti 25.06.2024 17:48

Привет @matiaslauriti, я поделился полным тестовым кодом.

Adefowowe 25.06.2024 19:09

Другие вопросы по теме