Testing e-mails in Laravel
[00:00] But back where? If we were in the browser, back would mean back to the contact page but we don't really have that in our test do we?
[00:12] Pretty much every application you build needs to send an email at some point. As an example, we have a minimal contact form that when submitted should take the message and forward it as an email to the owner of the application.
[00:25] Here inside my web routes file we have a contact endpoint that points to the contact controller show action, which returns a contact blade view. If I open the blade view and fold the classes, we see we have a basic form with a name, email, and message fields.
[00:44] Let's configure this form to make a post request to the same contact endpoint. I'll go here and say action slash contact, and method post. Now let's go to our routes file and add a new route. It will be a post to slash contact, which will call the contact controller store action.
[01:09] Let's add the store action, and for now we'll just dump whatever we receive in the request; so request all. Let's refresh the page, fill it with some gibberish, and click submit. we get a 419 page expired response. The reason for this is we forgot to include a csrf token with the request. The csrf token is a security mechanism that ensures the requests are being made from somewhere inside our application. So whenever you get the 419 response, make sure you are sending a csrf token as well.
[01:44] To include the token we can go to our contact blade file, and here, inside our form element, we can add @csrf
. Let's try filling the form again, I'll press submit, and here's our data. However, this hardly looks like a valid email address; we'll need to add some validation rules. Let's go inside our controller and do request validate, and then pass an array of rules. We'll do name, which is required, email, which is also required and needs to be a valid email, and the message, which is required. Let's try submitting that form again, and we're not getting the dumped data, which means our validation did its job and stop the execution.
[02:42] It would be great to show the error messages, though. So let's go inside our blade view and here, below the input, we'll use the error directive to check if we have an error for the name field, and if so let's display it. Let's also style it a bit we'll do mt1 text red 500 text sm let's resubmit the form but without a full name and here it is. Let's copy and paste these for every input. We'll have email here, and message here. Refresh, submit, and here they are.
[03:34] Let's also add some front-end validation as well. We could do required here, type email, and required, and required as well. Refresh, submit, and here's our first error message, second one, the email one, and the last one.
[04:03] Moving on, let's go ahead and forward the contact message as an email to the owner of this application. Inside the contact controller store action we'll use the mail facade, call the to method, and this method will receive the email address we want to send this email to. Generally, the application owner email address should live inside the mail configuration file here under the from key. So we have mail from and address; but as you can see this points to an environment variable, so let's grab this, open our env file, search for MAIL_FROM_ADDRESS
and fill it in.
[04:46] Back to our controller, to grab the config file we can do config and then the name of the config file which is mail, the from key, and then the address key. Then we can call the send method which requires an object that abides to the mailable contract interface; we call that object mailable. To create a mailable object open your terminal and type in php artisan make:mail
and then pass the name of the mailable; in our case we'll call that ContactMail
. This will generate a new php class under app slash mail. Here it is.
[05:31] Now if we go to our controller, here inside the send method we'll create a new instance of that ContactMail
class, and we'll pass the name, email, and message, as constructor parameters. We'll do request name, email and message. finally we'll just redirect back.
[05:59] Inside the ContactMail
we'll need to accept those constructor parameters. We'll do string name, email, and message. Let's initialize the properties and then here, inside the build method, we'll do this subject, and this will be the subject of the email; let's say "new contact message" and then html; this will be the message, so this message; and finally we'll have from, which will take the email as the first parameter, and the name as the second parameter.
[06:45] Now, the easiest way to test at an email is being sent is to go to your env file and change the mail driver to log. This will make it so that each email sent by the application ends up in the laravel.log
file. So if we go into the browser, fill in the form with valid data, click submit and then open our laravel.log
file, here is our email. However, it would be nice to test this in a more automated way; so let's create a new test by opening our terminal and typing in php artisan make:test ContactControllerTest
.
[07:30] Let's open the test file and add a new test called send_contact_email
. Whenever you want to test emails make sure you instruct laravel to fake them; you'll do mail using the facade, and then call the fake method. This will allow us to make assertions and see what emails have been sent or not. Then we need to perform whatever action will send the email, in our case we need to make a post request to the slash contact url with name, email, and message.
[08:17] As for the assertions, we need to assert that the application sent the email and redirected us back. But back where? If we were in the browser, back would mean back to the contact page; but we don't really have that in our test, do we? However, we can specify a starting point, so to speak, by calling the from method. So we can do from, and say /contact
; this will cause the request to act as if it was submitted from the contact page. Then we can store the response in a response variable and assert that we've been redirected back to the contact page. To assert that the email was successfully sent, we can use the mail and then assert send method and pass it the mailable class as a parameter.
[09:14] If we open the terminal and run this test, it surely passes. However, if you're a little bit paranoid you can also add additional checks by passing a function as a second parameter that will receive the mail, and you can check, for example, that the mail email matches the email address you pass through the request.
[09:41] If I rerun the test it fails because the email property is private. Let's turn that into a public one, rerun the test, and here it is.
-
1Testing file downloads05:29
-
2Testing file uploads07:34
-
3Testing file deletions in Laravel Applications04:03
-
4Testing emails with Mailhog02:35
-
5The only reasonable, cost-effective way to test validation in Laravel applications06:53
-
6Use data providers to reduce test duplication08:56
-
Testing e-mails in Laravel09:56
-
1Testing file downloads05:29
-
2Testing file uploads07:34
-
3Testing file deletions in Laravel Applications04:03
-
4Testing emails with Mailhog02:35
-
5The only reasonable, cost-effective way to test validation in Laravel applications06:53
-
6Use data providers to reduce test duplication08:56
-
Testing e-mails in Laravel09:56