Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bwrigley's avatar

Mockery executing original method

I'm struggling to understand why this isn't working as I'm sure I've had it working before.

In my feature test:

$product =  factory(Product::class)->create(['name' => 'Test', 'stripe_id' => 'Test']);
$plan =  factory(Plan::class)->create(['stripe_id' => 'Test', 'product_id' => $product->id]);

$mockPlan = Mockery::mock(Plan::class);
$this->app->instance(Plan::class, $mockPlan);
$mockPlan->shouldReceive('testMethod')
    ->andReturn('mock executed');

$subscriptionDetails = [
    'plan' => $plan->nickname,
    'stripeToken' => 'randomTokenString',
];

$this->fromUrl('/dashboard')
    ->post('/subscription/create', $subscriptionDetails)
    ->assertRedirect('/dashboard');

in my Plan class:

public function testMethod()
{
    return 'original method executed';
}

in a class that is executed during the test:

    $plan = Plan::whereNickname($request->plan)->first();

    dump($plan->testMethod());

the output is always: "original method executed"

I'm sure I'm missing something obvious, but can't see anything in the Laravel or Mockery docs that show to do anything different.

Thanks for your help in advance!

0 likes
6 replies
Talinon's avatar

@bwrigley

I think the problem is here:

$plan = Plan::whereNickname($request->plan)->first();

You are retrieving a model instance by directly referencing Plan instead of the instance in the container. Try this instead:

$plan = resolve(Plan::class);

// or

$plan = $this->app->make(Plan::class);

dump($plan->testMethod());

Make sure you import the full namespace for Plan in both your test and the class you resolve it in.

1 like
bwrigley's avatar

@talinon thank you for replying!

I'm not sure I fully get what you mean here sorry. The line:

$plan = Plan::whereNickname($request->plan)->first();

is just a line in my normal code base in a controller called SubscriptionController where I handle users subscribing to product through stripe. This is not a line in my test class.

My test replicates a user creating a new subscription by posting the correct form to a specific route, which then activates the controller.

In my test, I don't want it to actually hit Stripe's API so I'm mocking out various methods on a couple of controllers to emulate the Stripe responses.

After my controller gets the correct response, it provisions various permissions to the user depending on the package bought.

It's this provisioning that I want to test. However, I am still hitting Stripe each time.

I am just replicating the issue with this testMethod() example. I hope that makes sense?

JohnBraun's avatar

@bwrigley You are now mocking the eloquent model, while I would suggest mocking the payment class itself. And I think you would even be better of with a Fake instead of a mock.

Create an interface

One option, is to create a PaymentGatewayInterface. This interface can be implemented by a StripePaymentGateway and a FakePaymentGateway.
// PaymentGateWayInterface.php class:
interface PaymentGatewayInterface
{
    //
}


// StripePaymentGateway.php class:

class StripePaymentGateway implements PaymentGatewayInterface
{
    public function pay()
    {
        // logic for payment
    }
    // ...
}

// FakePaymentGateway.php class:

class FakePaymentGateway implements PaymentGatewayInterface
{
    public function pay()
    {
        return 'fake method called';
    }
    // ...
}

Define default concrete implementation

In the service container you bind the StripePaymentGateway as the concrete implementation of the PaymentGatewayInterface.
// AppServiceProvider

public function register()
{
    $this->app->bind(PaymentGatewayInterface::class, StripePaymentGateway::class);
}

Inject the interface

So, when your model needs an instance of a PaymentGatewayInterface, it will be resolved (via dependency injection) to your StripePaymentGateway concrete class.
// Eloquent model

public function testMethod(PaymentGatewayInterface $paymentGateway)
{
    $paymentGateway->pay();
}

Swap interface to fake concrete implementation

Now, in your tests, you can swap the binding in the service container to use the FakePaymentGateway whenever an instance of the PaymentGatewayInterface is requested.
// Test class

// ...

$this->app->instance(PaymentGatewayInterface::class, FakePaymentGateway::class);

// your post request here

Also, Adam Wathan has a great course on TDD with Laravel, in which he discusses (amongst a plethora of other things) a lot of Stripe related testing. I can highly recommend https://testdrivenlaravel.com

bwrigley's avatar

hi @johnbraun

Thank you for your reply.

Actually yes I'm already doing that. I have a StripeGateway interface which I use in exactly that way and the mocking works fine.

The actual problem hits with the Cashier methods that are added to the User model.

So in my SubscriptionController my store method essentially subscribes the user to a new product and then sets up provisioning of any relevant permissions/dates etc.

For testing purposes the stripe subscription is mocked through the StripeGateway class as you suggest. When the provisioning happens I need to grab the correct start date for this new subscription. This happens on my User model:

   public function setProvisioningDate()
    {
    //

    $startPeriod = $this->subscription('plan')->asStripeSubscription()->current_period_start;
        $nextProvision = Carbon::now()->timestamp($startPeriod)->addMonth();
        $this->next_provisioning = $nextProvision;

    //
    }

the asStripeSubscription() line is a Cashier method which in turn calls the Stripe API.

so what I was actually trying to do was to mock $user->setProvisioningDate() so this line never executes in the test, but the method is being executed anyway.

Apologies, this sounds confusing now, but I hope it makes sense!

Talinon's avatar

@bwrigley

I have zero experience with Cashier, but this thread might be of use to you: https://laracasts.com/discuss/channels/testing/how-to-mock-a-user-with-a-cashierstripe-subscription

To return to your original question - yes, I'm aware it's part of your code base.

A lot of developers new to mocking have some incorrect notion that mocking something performs magic and automatically transforms your code base. It does not. In your test, you are creating a mock for $plan, but your base application doesn't have any knowledge of your mocked object. It doesn't dive into your code base and transform every reference to Plan to your mocked object. Whether your controller is invoked by live or a test, it's still going to give you a new object of Plan.

You did bind your mock into the container, which is fine, but you're still not resolving it when you're referencing Plan. That is why I said if you use resolve() it'll do what you expect. As @johnbraun mentioned, this isn't the best approach, but I was trying to answer why your testMethod() wasn't behaving as you expected.

bwrigley's avatar

hi @talinon,

Ah, thank you, that makes a lot of sense, and I am definitely guilty of being someone who thought there was some magic going on 😳, I assumed that at the point that classes were loaded into memory, the mock would override any methods with the same names.

Thanks for that link too, I had already spotted that and sadly he resolves without actually doing any mocking in the end.

Thanks again for your help.

Please or to participate in this conversation.