Test Expectations
Learn how to use expectations to decide if a test passes or fails.
Tests follow a pattern called Arrange, Act, and Assert (AAA).
-
Arrange: This means setting up the world for our test. For example, we might need to prepare some dummy data or log in as a user.
-
Act: This is where we take the action we want to test.
-
Assert: This is where we test the expected result of the action. The expectation decides whether the test passes or fails.
Jasmine syntax
Jasmine test suites are wrapped in a function called describe
. It takes two arguments: the title for the test suite and an anonymous function containing the unit tests. Each test is wrapped in a function called it
. This function takes two arguments: a description and an anonymous function containing the unit test. Jasmine provides matchers called toBe
, toEqual
, and toBeNull
which are used to check the result of the unit test.
toBe
: Checks if the result is identical to the expected value.toEqual
: Has the same value, or the same properties, as the expected value.toBeNull
: Checks if the result isnull
.
Our first test
Let’s look at our first Jasmine test for a function that formats prices. In the test, we call the function with the value of 150
. Then, we use the expect
method to test that the result is the value we expect, such as $1.50
. Click the “Run” button below to run our first Jasmine test.
// Function we want to test function formatCurrency(valueInCents) { const price = (valueInCents / 100).toFixed(2).toString(); return '$' + price; } // Test suite describe('Currency test', () => { it('formats a price', () => { const formatted = formatCurrency(150); expect(formatted).toEqual('$1.50'); }); });
The toEqual
and toBe
expectations
What is the difference between the toEqual
and toBe
expectations? For primitive types, like strings and numbers, these two expectations behave in the same way. The difference is when we compare objects. The toEqual
expectation checks if the keys and values are the same, while toBe
checks if we have a reference to the same object. Take a look at the example below. The function works how we expect, but the test will fail. Can you find a way to fix the test to make it pass?
// Function we want to test function save(user) { // Make a call to the API here return user; } // Test suite describe('Save test', () => { it('saves a user', () => { const user = { name: 'Jane Doe', email: 'jane@example.com' }; const result = save(user); expect(result).toBe({ name: 'Jane Doe', email: 'jane@example.com' }); }); });
There are a couple of solutions for this. When the save
function runs, it returns a reference to the user
object. So, we can use toBe
to write a test where we expect that the result will be a reference to the user
object.
const user = {name: 'Jane Doe',email: 'jane@example.com'};const result = save(user);expect(result).toBe(user);
Alternatively, we can use the toEqual
expectation, which is less strict. It goes through each key/value pair in the object and makes sure they match the expected value.
const user = {name: 'Jane Doe',email: 'jane@example.com'};const result = save(user);expect(result).toEqual({name: 'Jane Doe',email: 'jane@example.com'});
The .not
expectation
We can already do a lot using the toEqual
and toBe
expectations we’ve seen so far. Jasmine offers quite a few more expectations that will be helpful from time to time, such as the .not
expectation. We can chain .not
to negate the expectation after it. Take a look at the example below where the reverse
function is used to reverse a string on line 10. We expect that the result, which has been reversed, should not equal the original string.
// Function we want to test function reverse(value) { return value.split("").reverse().join(""); } // Test suite describe('Reverse test', () => { it('reverse a string', () => { const result = reverse("Hello world"); expect(result).not.toEqual("Hello world"); }); });
Here’s another example, where we have a clone
function. We use .not
to test if our function returns a copy of the original object. The clone
function copies all the key/value pairs into a new object. Our test ensures that the result
object is a new object rather than a reference to the original object.
Your task is to add another expectation to check if the result
object has the same key/value pairs as the original.
-
Try adding another expectation on line 19.
-
Rerun the code to make sure your new expectation passes.
// Function we want to test function clone(value) { return JSON.parse(JSON.stringify(value)); } // Test suite describe('Clone test', () => { it('copies an object', () => { const user = { name: 'Jane Doe', email: 'jane@example.com' }; const result = clone(user); expect(result).not.toBe(user); // Your code here }); });
The toBeNull
expectation
Another useful expectation is toBeNull
. Here’s an example with a getSetting
method that returns a value. Our test checks if the result is a null value.
// Function we want to test function getSetting() { return 'some-value'; } // Test suite describe('Settings test', () => { it('fetches a value from the settings', () => { let result = null; result = getSetting(); expect(result).not.toBeNull(); }); });
Comparisons
When testing number values, we can use the comparison methods toBeGreaterThan
and toBeLessThan
. These can be helpful for testing boundary conditions.
Coding exercise
We have a function that takes a string and converts it into uppercase. Using Jasmine, write a test that calls the function with a test value and verifies the result.
// Function we want to test function capitalize(value) { return value.toUpperCase(); } // Your code goes here
You can see the solution to the problem above by clicking on the “Show Solution” button below.
We now know how to write expectations that check a condition to determine whether the test should pass or fail.