Search code examples
phplaravelmockery

PHPUnit/Mockery - Partially mock a function


Say i have this function. In this function i create a Mollie payment using the Mollie Laravel API Wrapper. The function $molliePayment needs to be mocked as i don't want to call the API during testing. How can i achieve this?

 public static function create(string $price, string $description, string $redirectUrl, array $metadata)
    {
        $molliePayment = \Mollie\Laravel\Facades\Mollie::api()->payments->create([
            'amount' => [
                'currency' => 'EUR',
                'value' => number_format((float) $price, 2, '.', ''),
            ],
        ]);

        $payment->update(['mollie_id' => $molliePayment->id]);

        // redirect customer to Mollie checkout page
        redirect($molliePayment->getCheckoutUrl(), 303);

        return ['status' => 200];
    }

How can i mock this API call?

\Mollie\Laravel\Facades\Mollie::api()->payments->create([])

This is my test:

test('Mollie creates new payment', function ($order, $mollie) {

    // Mock Mollie API here

    // Call binded Mollie class
    (new App\Services\MollieService())->create(
        '10',
        'Test payment',
        'https://google.nl',
        ['team' => $order['team']]
    );

    $this->assertDatabaseHas('payments', [
        'price' => $mollie['amount']['value'],
    ]);

})->with('order', 'mollie');

Edit:

I've tried Mocking the facade using:

// Mock Mollie API here
   \Mollie\Laravel\Facades\Mollie::shouldReceive('api')->once()->andReturnSelf();

But when i do, i get the error:

Undefined property: Mockery_2_Mollie_Laravel_MollieManager::$payments

Solution

  • If you look through the code, you can actually see the api class, does not have properties but actually calls methods on the instance. This makes it possible to rewrite your logic into this.

    Mollie::api()->payments()->create([...]);
    

    This removes the property access, which makes it way more convenient to actually test. Mockery has a way to mock method chaining.

    Mollie::shouldReceive('api->payments->create')->andReturn($payment);
    

    To define the Payment, we have too look for what the api would return. This is also why mocking can be so hard, you have to understand how the calls are executed on code you do not own, while also understanding what it returns in combination with how you utilize the returned data.

    Then fill out the id and the checkout url, as you use these. Offcourse declare this before the shouldReceive() call. Links is a stdClass object, therefor the casting of the array.

    use Mollie\Api\Resources\Payment;
    
    $payment = new Payment();
    $payment->id = 42;
    $payment->links = (object)['checkout' => 'https://some.checkout.url'];
    

    Now you should be able to mock this facade combining this, with what you already have.