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

bwrigley's avatar

Mocking Cashier methods

Hello,

I'm sure I'm missing something obvious here. I'm using Cashier and Stripe for billing and I have a simple method for showing invoices retrieved from Stripe.

I want to write a really simple test to ensure my method is working correctly, Obviously, I'm not trying to test Cashier as that's not mine.

So my test is simply:

    public function test_index_method(): void
    {
        $this->get(route('invoice.index'))
                ->assertStatus(200)
                ->assertViewIs('invoice.index');
    }

and my method is

    public function index(User $user): View
    {
        $invoices = $user->invoices();

        return view('invoices.index', compact('invoices'));
    }

The trouble is that invoices() is on the Billable trait and so it cannot be mocked.

I'd be interested if anyone has a good solution to this? Thanks

0 likes
9 replies
bugsysha's avatar

I assume that you have correct pk_test and sk_test for Stripe in your testing environment. If so then you can put those keys in your development environment and create a subscription. Now when you did everything from the browser create a factory for Subscription model which will contain that specific Stripe ID while doing everything through browser. Then in your test you create a user and create subscription and do what you have in test right now.

That is the easiest way. Mocking can be also easy. You can save to a file what CurlClient returns and pass that as a return value to the mock. So you will always receive response that is valid for that test. You can mock even before, but it all depends on your use case and the level of details you want to test/assert.

bwrigley's avatar

@bugsysha thanks for your reply.

Actually for the purposes of this test I'm not interested in what Stripe returns, only that the method returns a correct view. I'd prefer it if the test didn't hit the Stripe API at all. So mocking seems the best solution.

However if I add mocking to the test...

        $this->mock(User::class, function ($mock) {
            $mock->shouldReceive('invoices')->andReturn(null)->once();
        })->makePartial();

        $this->get(route('invoice.index'))
            ->assertStatus(200)
            ->assertViewIs('invoice.index');

... it still hits the API.

I think this is the correct syntax though?

bugsysha's avatar
bugsysha
Best Answer
Level 61

Then create UserMock which extends User model and just put empty invoices method. But that is a pretty empty test if you do not care what main feature of that endpoint does.

Yeah, that is the correct syntax but you then need to create a partial mock of the user and it all depends how do you resolve that user from the app.

bugsysha's avatar

Maybe best approach if you want to test it like that is to extract a service class or repository class and mock that service/repository. That way you own that class and can mock it without any issues. That gives you reusability and more control than what you have with built in Cashier methods.

bwrigley's avatar

@bugsysha thanks again for the reply.

Yes it's a really simple test just to make sure the route is working.

I've gone for creating a mock class that extends User like you suggested, thank you.

For anyone who follows this same advice remember to override the $table name to ensure the relationships work:

class MockUser extends User
{

    /**
     * @var string $table
     */
    protected $table = 'users';

    ///

}

Thanks again!

bugsysha's avatar

I think that having that specific feature tests which test tiny portions of app just complicate maintenance later on. But glad you found a solution.

bwrigley's avatar

@bugsysha thank you. I'd be interested to know more please.

There really isn't any logic in that method that warrants testing, as it's not mine. But I would like to a test to make sure that the route is available and is returning a view and a 200.

Is it that this feel more like a unit test? I guess as it's a controller method, I thought this was really the only to way to test it.

bugsysha's avatar

@bwrigley

I'd be interested to know more please.

It makes sense to me to have that kind of test if you can somehow create one test and let it test all routes on your app, but that gets messy very fast. If not then I would discard it. I've had similar approach when I started testing but then when something changes you have to fix same issue in multiple places since you will certainly add a test to see what happens when you have data in that invoices method.

Lately I've stopped writing tests that prove negative statements cause it is:

  • easier to read and understand positive one especially when you work in a multinational team with no native english speaking members,
  • and you can come up with a bunch of cases which prove negative scenarios but problem is that they have no value to non technical people in company, they take time to maintain and you can never come up with all cases

Example for this istest_guest_can_not_access_some_url. That is guarded by middleware which I do not own and there is no point for me to test it since it is a built in feature which if it had any issues someone would have caught it by now and fix it if we take into account how many people use same framework.

I know every company says in their ads that they want TDD, but the truth is that they do not care about that and they want you to do as much as possible as fast as possible. That mostly comes down to cutting test time (writing/maintaining) since that is nothing business side of company cares about. They just want less bugs and more features.

There really isn't any logic in that method that warrants testing, as it's not mine. But I would like to a test to make sure that the route is available and is returning a view and a 200.

The way you've picked then breaks the rule Do not mock what you do not own. You do not own that billable trait which provides you with invoices method, and you do not own User model cause it ships with the framework, and you just mocked it.

That is why you should go with a service class or repository so you can mock since you will be introducing that class. Even if it is a class with one method that calls that same logic you have in your controller. I guess I should not point out what improvements does it bring to the code if you introduce a new class to handle that case.

Is it that this feel more like a unit test? I guess as it's a controller method, I thought this was really the only to way to test it.

There is no clear line what is unit and what is feature test. I guess that unit way to test this if you wrote something like this:

public function test_can_not_come_up_with_a_name_at_this_moment(): void
{
    $controller = new SomeController();
    $user = new User();
    $response = $controller->index($user);
    $this->assertSomething($response);
}

The code above would be a living hell if that controller or method gets complicated as they tend to in real life. That is why it feels to me more as feature test since you are accessing it as user of that application would, from outside. And when you access it from outside you trigger a bunch of features which then more feels like a feature test. That way you cover more ground.

What I think is that it makes sense to have unit test if you are building a calculator class, cause everything is probably contained within one class and it is transparent cause result depends directly on what you pass to it. If you do not have that kind of code then you are better of with feature test, since they are easier to write. You have your route, you know which data you need to send via request and you know what changes that request caused in the database. If you have any events, just assert that event was fired and write separate feature test for listeners that wait for that event.

Hope all this makes any sense, I'm still struggling to write any articulate text in english.

1 like

Please or to participate in this conversation.