Scroll down to the script below, click on any sentence (including terminal blocks!) to jump to that spot in the video!
With a Subscription, click any sentence in the script to jump to that part of the video!Login Subscribe
I also want to test the method that sends the weekly update email. But because the real complexity of this method is centered around generating the PDF, instead of a unit test, let's write an integration test.
MailerTest, add a second method:
testIntegrationSendAuthorWeeklyReportMessage(). Let's start the same way as the first method: copy all of its code except for the asserts, paste them down here and change the method to
sendAuthorWeeklyReportMessage(). This needs a
User object... but it also needs an array of articles. Let's create one:
$article = new Article(). These articles are passed to the template where we print their title. So let's at least populate that property:
Black Holes: Ultimate Party Pooper
Use this for the 2nd argument of
sendAuthorWeeklyReportMessage(): an array with just this inside.
It's time to think strategically about our mocks. Right now, every dependency is mocked, which means it's a pure unit test. If we kept doing this, we could probably make sure that whatever
render() returns is passed to the PDF function... and even assert that whatever that returns is passed to the
attach() method. It's not bad, but because the logic in this method isn't terribly complex, its usefulness is limited.
What really scares me is the PDF generation: does my Twig template render correctly? Does the PDF generation process work... and do I really get back PDF content? To test this, instead of mocking
wkhtmltopdf installed on this machine, otherwise my tests will fail. Tradeoffs!
So here's the plan: use the real
$entrypointLookup... because I don't really want to send emails... and the
$entrypointLookup doesn't matter unless I want to test that it does reset things correctly between rendering 2 PDFs.
To make this test able to use real objects, we need to change
KernelTestCase. That class extends the normal
TestCase but gives us the ability to boot Symfony's service container in the background. Specifically, it gives us the ability, down in the method, to say:
self::bootKernel(). That will give us the ability to fetch real service objects and use them.
So we'll leave
$symfonyMailer mocked, leave the
$entrypointLookup mocked, but for the
$pdf = self::$container -
bootKernel() set that property -
->get() passing this
Pdf::class. Do the same for Twig:
I love that! Again, the downside is that you really do need to have
wkhtmltopdf installed correctly anywhere you run your tests. That's the cost of doing this.
Before we try it, at the bottom, we don't have any asserts yet. Let's add at least one:
$this->assertCount() that 1 is the count of
We could go further and look closer at the attachment... maybe make sure that it looks like it's in a PDF format... but this is a good start.
Now let's try this. Find your terminal and run our normal:
It is slower this time... and then.. ah! What just happened? Two things. First, because this booted up a lot more code, we're seeing a ton of deprecation warnings. These are annoying... but we can ignore them.
The second thing is that... the test failed! But... weird - not how I expected: something about APCu is not enabled. Huh? Why is it suddenly trying to use APCu?
The cause of this is specific to our app... but it's an interesting situation. Open up
config/packages/cache.yaml. See this
app key? This is where you can tell Symfony where it should store things that need to be added to cache at runtime - like the filesystem, redis or APCu. In an earlier tutorial, we set this to a parameter that we invented:
This allows us to do something cool. Open
config/services.yaml. Here, we set
cache.adapter.apcu: we told Symfony to store cache in APCu. And... apparently, I don't have that extension installed on my local machine.
Ok... fine... but then... how the heck is the website working? Shouldn't we be getting this error everywhere? Yep... except that we override this value in
services_dev.yaml - a file that is only loaded in the
dev environment. Here we tell it to use
This is great! It means that we don't need any special extension for the cache system while developing... but on production, we use the superior APCu.
The problem now is that, when we run our tests, those are run in the
test environment... and since the
test environment doesn't load
services_dev.yaml, it's using the default APCu adapter! By the way, there is a
services_test.yaml file... but it has nothing in it. In fact, you can delete this: it's for a feature that's not needed anymore.
So, honestly... I should have set this all up better. And now, I will. Change the default cache adapter to
cache.adapter.filesystem. Then, only in the
prod environment, let's change this to
apcu. To do that, rename
services_prod.yaml... and change the parameter inside to
test environment should use the filesystem. Let's try it!
And... if you ignore the deprecations... it worked! It actually generated the PDF inside the test! To totally prove it, real quick, in the test,
var_dump($email->getAttachments())... and run the test again:
Yea! It's so ugly. The attachment is some
DataPart object and you can see the crazy PDF content inside. Go take off that dump.
Ok, the last type of test is a functional test. And this is where things get more interesting... especially in relation to Mailer. If we want to make a functional test for the registration form... do we expect our test to send a real email? Or should we disable email delivery somehow while testing? And, in both cases, is it possible to submit the registration form in a functional test and then assert that an email was in fact sent? Ooo. This is good stuff!