7 min read

Web developers work hard to build a working application that they can be proud of. But how can we ensure a painless maintainability in the future? A comprehensive automated testing layer will become our lifeline once our application begins to scale up and we have to mitigate the impact of bugs caused by new functionalities colliding with the already existing ones. In this article, we will see why we need to perform unit testing and the process of setting up Jasmine for our tests.

This tutorial is an excerpt taken from the book ‘Learning Angular – second edition’ written by Christoffer Noring, Pablo Deeleman. 

Why do we need unit tests?

What is a unit test? Unit tests are part of an engineering philosophy that takes a stand for efficient and agile development processes, by adding an additional layer of automated testing to the code, before it is developed. The core concept is that each piece of code is delivered with its own test, and both pieces of code are built by the developer who is working on that code. First, we design the test against the module we want to deliver, checking the accuracy of its output and behavior. Since the module is still not implemented, the test will fail. Hence, our job is to build the module in such a way that it passes its own test.

Unit testing is quite controversial. While there is a common agreement about how beneficial test-driven development for ensuring code quality and maintenance over time is, not everybody undertakes unit testing in their daily practice. Why is that? Well, building tests while we develop our code can feel like a burden sometimes, particularly when the test winds up being bigger in size than the piece of functionality it aims to test.

However, the arguments favoring testing outnumber the arguments against it:

  • Building tests contribute to better code design. Our code must conform to the test requirements and not the other way around. In that sense, if we try to test an existing piece of code and we find ourselves blocked at some point, chances are that the piece of code we aim to test is not well designed and shows off a convoluted interface that requires some rethinking. On the other hand, building testable modules can help with early detection of side effects on other modules.
  • Refactoring tested code is the lifeline against introducing bugs in later stages. Any development is meant to evolve with time, and on every refactor the risk of introducing a bug, that will only pop up in another part of our application, is high. Unit tests are a good way to ensure that we catch bugs at an early stage, either when introducing new features or when updating existing ones.
  • Building tests is a good way to document our code APIs and functionalities. And this becomes a priceless resource when someone not acquainted with the code base takes over the development endeavor.

These are only a few arguments, but you can find countless resources on the web about the benefits of testing your code. If you do not feel convinced yet, give it a try. Otherwise, let’s continue with our journey and see the overall form of a test.

Unit testing in Jasmine

There are many different ways to test a piece of code, but in this article, we will look at the anatomy of a test, what it is made up of. The first thing we need, for testing any code, is a test framework. The test framework should provide utility functions for building test suites, containing one or several test specs each. So what are these concepts?

  • Test suite: A suite creates a logical grouping for a bunch of tests. A suite can, for example, be all the tests for a product page.
  • Test spec: This is another name for a unit test.

The following shows what a test file can look like where we are using a test suite and placing a number of related tests inside. The chosen framework for this is Jasmine. In Jasmine, the describe() function helps us to define a test suite. The describe() method takes a name as the first parameter and a function as the second parameter. Inside of the describe() function are a number of invocations to the it() method. The it() function is our unit test; it takes the name of the test as the first parameter and a function as the second parameter:

// Test suite
describe('A math library', () => { 
  // Test spec
  it('add(1,1,) should return 2', () => {
    // Test spec implementation goes here
  });
});

Each test spec checks out a specific functionality of the feature described in the suite description argument and declares one or several expectations in its body. Each expectation takes a value, which we call the expected value, and is compared against an actual value by means of a matcher function, which checks whether expected and actual values match accordingly. This is what we call an assertion, and the test framework will pass or fail the spec depending on the result of such assertions. The code is as follows:

// Test suite
describe('A math library', () => {
  // Test spec
  it('add(1,1) should return 2', () => {
    // Test assertion
    expect(add(1,1,)).toBe(2);
  });
it('subtract(2,1)', () =>{
//Test assertion
expect(subtract(2,1)).toBe(1);
})
});

In the previous example, add(1,1) will return the actual value that is supposed to match the expected value declared in the toBe() matcher function.

Worth noting from the previous example is the addition of a second test that tests our subtract() function. We can clearly see that this test deals with yet another mathematical operation, thus it makes sense to group both these tests under one suite.

So far, we have learned about test suites and how to group tests according to their function. Furthermore, we have learned about invoking the code you want to test and asserting that it does what you think it does. There are, however, more concepts to a unit test worth knowing about, namely setup and teardown functionality. A setup functionality is something that sets up your code before the test is run usually.

It’s a way to keep your code cleaner so you can focus on just invoking the code and asserting. A tear-down functionality is the opposite of a setup functionality and is dedicated to tearing down what you set up initially; essentially it’s a way to clean up after the test. Let’s see how this can look in practice with a code example, using the Jasmine framework. In Jasmine, the beforeEach() method is used for setup functionality; it runs before every unit test. The afterEach() method is used to run tear-down logic. The code is as follows:

describe('a Product service', () => {
  let productService;
beforeEach(() => {
productService = new ProductService(); 
});

it('should return data', () => {
let actual = productService.getData();
assert(actual.length).toBe(1);
});

afterEach(() => {
productService = null; 
});
});

We can see in the preceding code how the beforeEach() function is responsible for instantiating the productService, which means the test only has to care about invoking production code and asserting the outcome. This makes the test look cleaner. It should be said, though, in reality, tests tend to have a lot of setup going on and having a beforeEach() function can really make the tests look cleaner; above all, it tends to make it easier to add new tests, which is great. What you want at the end of the day is well-tested code; the easier it is to write and maintain such code, the better for your software.

Testing web applications in general and Angular applications, in particular, poses a myriad of scenarios that usually need a specific approach. Remember that if a specific test requires a cumbersome and convoluted solution, we are probably facing a good case for a module redesign instead.

There are several paths to compound our knowledge of web application testing in Angular and enable us to become great testing ninjas.  In this we saw the importance of unit testing in our Angular applications, the basic shape of a unit test, and the process of setting up Jasmine for our tests.

If you found this post useful, do check out the book ‘Learning Angular – Second Edition’  to learn more about unit testing and how to implement it for routes, inputs, outputs, directives, etc.

Read Next

Everything new in Angular 6: Angular Elements, CLI commands and more

Angular 6 is here packed with exciting new features!

Getting started with Angular CLI and build your first Angular Component


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

LEAVE A REPLY

Please enter your comment!
Please enter your name here