×
By the end of this chapter, you should be able to:
Let's look at some more test code. Feel free to copy and paste this into our setup from above:
let arr; beforeEach(function(){ arr = [1,3,5]; }); describe("Arrays", function(){ describe("#push", function(){ it("adds elements to an array", function(){ arr.push(7); expect(arr).toEqual([1,3,5,7]); }); it("returns the new length of the array", function(){ expect(arr.push(7)).toBe(4); }); it("adds anything into the array", function(){ expect(arr.push({})).toBe(4); }); }); });
There are a couple new things going on here; let's investigate!
First, what is this beforeEach
thing? Sometimes before each it
block, we want to initialize some code so that the setup before running the test is the same. In the above example, our first test mutates the arr
array by adding the number 7 to it. Rather than having to keep track of that mutation across all subsequent tests, it's easier to just set arr
equal to the same array before each test runs. In other words, we should always strive to not have our tests change the data we are working with in other tests. beforeEach
is a great way to save us from writing arr = [1,3,5]
before every single block. Here's how the above code would need to look if we didn't use a beforeEach
:
// WITHOUT BEFORE EACH (notice how many times we repeat let arr = [1,3,5]) describe("Arrays", function(){ describe("#push", function(){ it("adds elements to an array", function(){ let arr = [1,3,5]; arr.push(7); expect(arr).toEqual([1,3,5,7]); }); it("returns the new length of the array", function(){ var arr = [1,3,5]; expect(arr.push(7)).toBe(4); }); it("adds anything into the array", function(){ var arr = [1,3,5]; expect(arr.push({})).toBe(4); }); }); });
Second, what is toEqual
and how is it different than toBe
? Remember, when we try to compare objects (including arrays) in JavaScript, only the reference is checked! If you have two arrays, using a ==
or ===
comparison won't tell you whether or not those arrays have the same values:
var numbers = [1,2,3]; var numbersCopy = numbers; var numbersOtherCopy = [1,2,3]; numbers === numbersCopy; // true, since both variables refer to the same object in memory numbers === numbersOtherCopy // false! even though both arrays have the same structure, they are not the same array in memory.
So, when writing tests, how can we see if two arrays or objects consist of the same values? To do that in our tests we use deep.equal
. Deep equality checks whether the elements in two arrays are equal, rather than simply checking if the two arrays refer to the same place in memory. Along with things like toEqual
, jasmine
has many many more operators to check for all types of things about our data - it's quite a versatile tool!
Once you get used to writing tests, you can use a workflow very common in TDD. This workflow is called "Red, Green, Refactor," and goes like this:
Let's walk through this process with a simple example. Suppose you wanted to write a function called onlyStrings
which takes in an array, and returns only the elements in the array that are strings. Here are some tests you might want to write:
describe("onlyStrings", function(){ it("returns an array", function(){ expect(onlyStrings([1,2,3])).toEqual(jasmine.any(Array)); }); it("does not change arrays of strings", function(){ expect(onlyStrings(["a","b","c"])).toEqual(["a","b","c"]); }); it("removes non-string primitives from an array", function(){ expect(onlyStrings([1,"hi",null,"cool",undefined,"woah",false,"ok"])).toEqual(["hi","cool","woah","ok"]); }); it("removes reference types from an array", function(){ expect(onlyStrings([{},"a",[],"b",function(){},"c"])).toEqual(["a","b","c"]); }); });
After writing the tests, you would then write the onlyStrings
function. Here's one possible implementation:
function onlyStrings(arr) { let strings = []; for (let i = 0; i < arr.length; i++) { if (typeof arr[i] === "string") strings.push(arr[i]); } return strings; }
With this code, the tests should pass. Upon further reflection, however, you may decide to refactor this function so that it uses filter
instead:
function onlyStrings(arr) { return arr.filter(function(el) { return typeof el === "string"; }); }
With this implementation, the tests still pass, and you can be fairly certain that your changes to the onlyStrings
function haven't introduced any new bugs.
As you're reading about testing, you're likely to come across two different kinds of tests: unit tests and integration tests. As you're first writing tests, you'll probably be writing mostly unit tests. These are tests which are written for one small component of your application, e.g. one function. They're meant to test the individual pieces, or units of your application. Integration tests, by contrast, are meant to test the system as a whole, and ensure that different pieces of the application are working correctly. The distinction isn't terribly important right now, but it's good to know what the terms mean and how they're different. For more on this, check out this Stack Overflow article.
When you're ready, move on to Testing Exercises