Story points | 8 |
Tags | tdd mocks environmental variables smtp deprecated |
Hard Prerequisites | |
IMPORTANT: Please review these prerequisites, they include important information that will help you with this content. | |
|
This project should demonstrate your understanding of mocks/spies.
We’ll be making an application that sends emails.
Step 1 is to sign up for Brevo. You can sign up for the free plan. Once you are logged in click on the drop down near the profile avatar at the top right of the page and click on SMTP & API. This is where you will find the SMTP settings. It’ll look something like this:
SMTP server: smtp-relay.brevo.com
Port: 587
Login: ???
Password: ?????
Note, that SMTP isn’t the most super secure way to authenticate with the email servers. But it’s the simplest way. For now, let’s keep it simple. We’re here to practice unit testing.
Take these settings and save them in a shell script called smtp_secrets.sh
. It should look like this.
#!/bin/sh
export SMTP_SERVER=smtp-relay.brevo.com
export SMTP_PORT=587
export SMTP_LOGIN=???
export SMTP_PASSWORD=?????
Why? Because we don’t mix code and configuration. Your code should be able to access the configuration when it needs to.
Now add the following line to your .gitignore
file:
smtp_secrets.sh
Why? Because these credentials should be kept secret. If you commit them to your git repo and push it to Github then anyone who has access to your repo can read your secrets and start sending emails from your account.
This remains true even if you make a commit that removes the secrets from the repo.
So far so good.
Now try this out in the terminal
source smtp_secrets.sh
#############################
# if you are running Python #
#############################
python3
# now you are in a python shell
import os
SMTP_SERVER = os.getenv('SMTP_SERVER')
print(SMTP_SERVER)
###########################
# if you are running Node #
###########################
node
# now you are in a node shell
const SMTP_SERVER = process.env.SMTP_SERVER;
console.log(SMTP_SERVER)
###########################
# if you are running Java #
###########################
jshell
String env = System.getenv("smtp") | System.getProperty("smtp")
System.out.print(env)
Ok, what just happened?
When you open up a new terminal you are running bash (or some variation thereof). Bash is a programming language and has variables too. When you define bash variables you can choose to export them. Exporting a bash variable makes it accessible to other programs running in the same terminal.
So when we call source smtp_secrets.sh
, bash makes a few variables and makes sure that if you launch another application then those secrets are available.
Cool eh?
Write a program that sends a random inspirational quote to an email address, you should export 2 functions from a file named send_email.js
:
handle send email
(use the appropriate naming conventions for the language you are using) - should take in a parameter which is the recipients email addresses, and should be the only function responsible for sending the email via SMTP.send email
(use the appropriate naming conventions for the language you are using) - is the function that gets called when you execute the script from the command line. It needs to do something like this:// 1. get the emails from the command line parameters
emails = ...;
// 2. call the function responsible for sending the random quote with those emails
handleSendMail(emails);
The reason we use separate functions is that we want to make sure our functionality is as reusable and testable as possible. If you have one function that grabs the email address from the command line arguments and then immediately sends the email then that function would only be able to be used from the command line. You wouldn’t be able to use it in other places.
You should have a list of quotes in a file by itself. Your program should grab one and send the email.
Your final email quotes should be formatted like this:
"The only true measure of success is the number of people you have helped" - Ray Dalio
Your directory structure should look like this.
├── spec
| ├── support
| | └── jasmine.json
| └── send_email_spec.js
├── src
| └── send_email.js
└── package.json
After npm init
you should add a script named send_inspiration
to package.json
. Look for scripts
inside the file.
you should be able to do the following:
npm run send_inspiration ...
The command needs to allow you to pass in an email address from the command line. Take a look at this
Also, note that good code is written to be reusable. Make sure all your executable code is inside useful functions. When we run npm run send_inspiration
then the right functions will need to be executed.
Make use of nodemailer to make it easier for yourself to send emails.
You should be able to run your code using
python send_inspiration.py ...
The command needs to allow you to pass in an email address from the command line. Take a look at this
Go with the simplest option you can find initially - because KISS
Also, note that good code is written to be reusable. Make sure all your executable code is inside useful functions. When we run npm run send_inspiration
then the right functions will need to be executed.
Running it as part of your main application should be ok
Your unit tests should make sure that when the application runs then it sends one email with one quote to one person.
If your tests
then they are wrong.
Please make sure that you understand what mocks and spies are for when writing your tests. These things do not exist to take up space, waste time or look fancy. They do have a purpose.
If you do anything that looks like the following pseudocode then you are doing it wrong:
spyOn(myFunction)
myFunction()
assert myFunction.wasCalledOnce
Here is a useful way to think about mocks and spies:
Sending emails is considered “expensive”. Why? Because it costs actual money if you do it in bulk. Also, it costs time. It is not a fast and free operation. But it is an important operation.
You need to test that when you want to send an email then:
You don’t want your unit tests to send emails. You just want them to prove that the part of your machine that sends emails is being used correctly.
As another example, if you were developing a “forgot password” or “confirm email address” function for a website then you would test pretty much the same thing. You need to make sure that the correct functionality gets evoked, without actually sending anything.