NoelDeMartin / laravel-dusk-mocking

Mock facades in Laravel Dusk tests
MIT License
34 stars 4 forks source link

How to mock custom service class injected to a controller #33

Open expondo opened 1 month ago

expondo commented 1 month ago

Hello,

I have some class called InvoiceDownloadService. This class is passed to a controller and injected automatically by laravel. I prepared fake class for dusk test like below:

    $this->browse(function (Browser $browser) {
        $browser->registerFake(InvoiceDownloadService::class, InvoiceDownloadServiceFake::class);
        $browser->mock(InvoiceDownloadService::class);
        ....
    });

but it results with error: ERROR: Call to undefined method InvoiceDownloadService::swap() {"exception":"[object] (Error(code: 0): Call to undefined method InvoiceDownloadService::swap() at vendor/noeldemartin/laravel-dusk-mocking/src/Driver.php:77)

As i'm understand you comment here https://github.com/NoelDeMartin/laravel-dusk-mocking/issues/31#issuecomment-1439537698 it should just works. But it don't.

It is possible or not? Or your comment are for serviceProviders who extends ServiceProvider (Laravel) only?

Regards.

NoelDeMartin commented 1 month ago

Hey, thanks for opening an issue.

Now that I've looked at that old comment, I think what I said wasn't completely right 😅. What this package and Laravel's ::fake() methods are faking is Facades, not service providers. For example, when you're faking emails in Laravel you do Mail::fake(), not MailManager::fake().

What that means, in practice, is that you should create a Facade for your service, and use it through the Facade instead of injecting it in the controller. I guess you could call it InvoiceDownload?

This should work:

$this->browse(function (Browser $browser) {
    $browser->registerFake(InvoiceDownload::class, InvoiceDownloadServiceFake::class);
    $browser->mock(InvoiceDownload::class);
    // ...
});

Under the hood, Facades are using the Service Container as well, so it should be possible to refactor this package to make it work in your situation as well. But seeing that Laravel itself doesn't support this type of mocking, everything is done through Facades, it should be fine to keep this package's scope as it is.

expondo commented 1 month ago

Ok, now it is clear and correct with what I'm understood from doc. My problem is that in my project I'm do not want to use Facades for my services, but i'm understood that it is not easy to mock browser tests without using of Facades.

Thank you for the reply ;)

expondo commented 1 month ago

I just checked and it works:

            $browser->registerFake(\Facades\App\Service\InvoiceDownloadService::class, InvoiceDownloadServiceFake::class);
            $browser->mock(\Facades\App\Service\InvoiceDownloadService::class);

But still, not everyone want to use Facades in that way ;)

NoelDeMartin commented 1 month ago

Ok, as I said I think it's technically possible to do what you want, because everything is being resolved from the Service Container anyways. But with the current implementation it isn't supported, so I'm leaving this open as an enhancement.