Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Matheus Lopes Santos
Matheus Lopes Santos

Posted on

     

Mocking Browsershot library in your tests

In our journey as developers, we often find ourselves creating applications that need to export reports or pages to PDF format. For a long time, we used various libraries for this task, such asmPDF,FPDF,wkHtmlToPdf, among others. However, today, in my humble opinion, we have one of the best packages for PDF generation on the market, which isBrowsershot. It's straightforward to configure and generate PDF files.

But, here's the issue some devs face: How can I write tests for a class that uses Browsershot? Let's dive a bit deeper.

Imagine we have a class called GeneratePdf that takes a file name, a URL to render, and maybe the paper size as parameters. This class will save our PDF to AWS S3.

⚠️ The examples here are written in a Laravel application and using Pest for automated tests.

<?phpdeclare(strict_types=1);namespaceApp\Actions;useIlluminate\Support\Facades\Storage;useSpatie\Browsershot\Browsershot;classGeneratePdf{publicfunctionhandle(string$fileName,string$url,string$paperSize='A4'):string|false{$path='/exports/pdf/'.$fileName;$content=Browsershot::url($url)->format($paperSize)->noSandbox()->pdf();if(!Storage::disk('s3')->put($path,$content)){returnfalse;}return$path;}}
Enter fullscreen modeExit fullscreen mode

Fantastic! Our action will save the PDF and return the path so that we can use it in an email, save it in a database, and so on. The only responsibility of this class is to generate the PDF and return the path.

But now, how do we test this little guy?

Writing Our Tests

Okay, in this phase, let's write a simple test to see if everything works as expected.

it('should generate a pdf',function(){Storage::fake('s3');$pdf=(newGeneratePdf())->handle(fileName:'my-file-name.pdf',url:'https://www.google.com');Storage::disk('s3')->assertExists($pdf);});
Enter fullscreen modeExit fullscreen mode

However, you might notice that our test takes a while to execute. But why?

Our test took a while because Browsershot made a request togoogle.com to fetch its content and create the PDF for you.

Alright, it's just one test, what's the harm in that? Let's think:

  • What if there's more than one class using Browsershot?
  • What if you have no internet connection?The test fails.
  • What if you're using a paid pipeline service?The test will take longer, and you'll pay more for it.

So, how can we write our test more efficiently?

withMOCKERY ✨✨✨

Mockery

To simulate the behavior of a class, we can use theMockery library, which is already available in PHPUnit and Pest.

This library provides an interface where we can mimic or spy on our class's behavior to make assertions on the methods that were called.

But there's a problem (there always is), a static call...

BrowserShot::url(...)
Enter fullscreen modeExit fullscreen mode

The Problem with Static Methods

Static methods are great, especially for helper classes, such as a method that checks whether a CPF (Brazilian social security number) is valid or not. In such cases, since we won't have access to$this, we can make these methods static without any issues.

However, this comes at a cost...

Writing unit tests for static methods is straightforward. We call the method and make the necessary assertions, simple as that. But what if I need to mock a class that calls a static method and then calls its non-static methods?

According to the Mockerydocumentation, it doesn't support mocking public static methods. To work around this, there's a kind of hack to bypass this behavior, which involves creating an alias. (You can read more about ithere).

it('should generate a pdf',function(){Storage::fake('s3');mock('alias:'.Browsershot::class)->shouldReceive('url->format->noSandbox->pdf');$pdf=(newGeneratePdf())->handle(fileName:'my-file-name.pdf',url:'https://www.google.com');Storage::disk('s3')->assertExists($pdf);});
Enter fullscreen modeExit fullscreen mode

Alright, but what does this do? When we usealias:, we're telling Composer:

"Hey, when I need Browsershot, bring this one here to me, not the original class."

The catch is that even Mockery doesn't recommend usingalias: oroverload:. This can lead to class name collision errors and should be run in separate PHP processes to avoid this.

So, my friend, how do I write this test?

In fact, let's change the approach on how we use Browsershot :)

Dependency Analysis and Dependency Injection

By analyzing theBrowsershot::url method, we can discover what it does, and it's extremely simple.

publicstaticfunctionurl(string$url):static{return(newstatic())->setUrl($url);}
Enter fullscreen modeExit fullscreen mode

Great, to avoid usingalias: oroverload:, we can simply inject Browsershot into our class. Now, it looks like this:

<?phpdeclare(strict_types=1);namespaceApp\Actions;useIlluminate\Support\Facades\Storage;useSpatie\Browsershot\Browsershot;classGeneratePdf{publicfunction__construct(privateBrowsershot$browsershot){}publicfunctionhandle(string$fileName,string$url,string$paperSize='A4'):string|false{$path='/exports/pdf/'.$fileName;$content=$this->browsershot->setUrl($url)->format($paperSize)->noSandbox()->pdf();if(!Storage::disk('s3')->put($path,$content)){returnfalse;}return$path;}}
Enter fullscreen modeExit fullscreen mode

This way, mocking becomes much lighter and efficient:

it('should generate a pdf',function(){Storage::fake('s3');$mock=mock(Browsershot::class);$mock->shouldReceive('setUrl->format->noSandbox->save');$pdf=(newGeneratePdf($mock))->handle(fileName:'my-file-name.pdf',url:'https://www.google.com');Storage::disk('s3')->assertExists($pdf);});
Enter fullscreen modeExit fullscreen mode

If you're using Laravel, you can use the$this->mock method, which interacts directly with the framework's container.

Our test now looks like this:

it('should generate a pdf',function(){Storage::fake('s3');Storage::put('pdf/my-file-name.pdf','my-fake-file-content');$this->mock(Browsershot::class)->shouldReceive('setUrl->format->noSandbox->save');$pdf=app(GeneratePdf::class)->handle(fileName:'my-file-name.pdf',url:'https://www.google.com');Storage::disk('s3')->assertExists($pdf);});
Enter fullscreen modeExit fullscreen mode

By doing this, we make our class loosely coupled, allowing us to perform a wide range of tests without much hassle, and we get to use a powerful pattern, which is dependency injection.

Until next time, folks. 😗 🧀

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I'm a guy passionate about PHP and web development. I enjoy playing music, cycling, and watching some movies from time to time.
  • Work
    Tech Lead at DevSquad
  • Joined

More fromMatheus Lopes Santos

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp