In this section, which is the final section for the testing chapter, we'll learn some pretty advanced testing techniques. We'll be using these techniques as we build real-world apps, but for now let's start off with an example. We'll worry about the vocabulary for what we're about to do in just a second.
For the moment, we'll close all our current files and create a new directory in the root of the project. We'll make a new folder called spies. We'll talk about what exactly spies are and how they relate to testing in just a moment. Inside spies, we'll make two files: app.js (this is the file that we'll be testing) and a second one, called db.js. In our example, we can just assume that db.js is a file that has all sorts of methods for saving and reading data from the database.
Inside db.js, we'll create one function using module.exports. Let's create a function called saveUser. The saveUser function will be a really simple function, and it will take a user object like this:
module.exports.saveUser = (user) => {
}
Now, we'll just print it to the screen using the console.log statement. We'll print it a little message, Saving the user, and we'll also print out the object as shown here:
module.exports.saveUser = (user) => {
console.log('Saving the user', user);
}
Now obviously, this is not a real saveUser function. We do not interact with any sort of database, but it will illustrate exactly how we will be using spies to test our code.
Next up, we will fill our app.js, and this is the file we'll actually be testing. Inside app.js, we'll create a new function: module.exports.handleSignup. In the context of an application with authentication, handleSignup might take an email and a password; maybe it goes ahead and checks if the email already exists. If it doesn't, great; it saves the user and then it sends some sort of a welcome email. We can simulate that by creating an arrow function (=>) that takes in email and a password:
module.exports.handleSignup = (email, password) => {
};
Inside the arrow function (=>), we'll leave three comments. These will be things that the function is supposed to do. It will check if the email already exists; it will save the user to the database; and finally, we'll send that welcome email:
module.exports.handleSignup = (email, password) => {
// Check if email already exists
// Save the user to the database
// Send the welcome email
};
Now, these three things are just an example of what a handleSignup method might actually do. When we go through the real process, you'll see how it pans out. Now, we already have one of these in place. We just created saveUser, so we'll do is call saveUser instead of having this second comment:
// Check if email already exists
db.saveUser()
// Send the welcome email
It's not imported just yet, but that's not going to stop us from calling it; we'll add the import in just a second, and we'll pass in what it expects, the user object. Now, we don't have a user object; we have an email and a password. We can create that user object by setting email equal to the email argument and setting password equal to the password argument:
db.saveUser({
email: email,
password: password
});
Now one important thing to note: inside ES6, if the property name in an object you're setting is the same as the variable name, you can actually define it like this:
db.saveUser({
email,
password
});
In this example, since we're setting a password property equal to whatever on the password variable, there's no need to have both. This ES6 syntax also allows us to create a much simpler-looking call. There's no need to have it on multiple lines since it's pretty reasonable in length.
Now, at the top, we can load in db by creating a variable, calling it db, and setting it equal to require('db.js'). That is a local file, so we'll start it with a ./ to grab it from the current directory:
var db = require('./db.js');
Now, this is an example of something that we'll want to test inside our code. We have a handleSignup method. It takes an email and a password, and we need to make sure that db.saveUser works as well. That is a big problem, and this means that we're not just testing handleSignup, we are also testing the following:
- We're testing handleSignup
- We're testing our code that checks if an email exists
- Maybe that allows another function
- We're checking if the saveUser function works as expected
- we're checking if the welcome email is sent
This is a real pain. What we'll do instead is fake the saveUser function. It's never actually going to execute the code inside it db, but it will let us verify that when we run handleSignup, saveUser gets called. We're going to do this with something called spies.
The spies function let you swap out a real function such as saveUser for a testing utility. When that test function gets called we can create various assertions about it, making sure it was called with certain arguments. Let's start exploring that.