AngularJS Testing: Best Practices
| technology | programming - Erik Mikac

AngularJS Testing: Best Practices

One of the most important aspects of maintaining an agile environment is understanding the importance of unit tests. Long gone are the days when testing was optional, a "nice to have," or a stretch goal. Modern, agile front-end applications are ever-changing, deployed continuously, and complex. This could not be more true than it is with an Angular application. Whether you are an experienced developer or new to AngularJS, there's a lot to love.

Angular is an MVC framework that incorporates many moving parts: back-end database calls, HTML rendering, and JavaScript business logic. The customer's needs may be inconsistent, which means that any of the capabilities mentioned earlier need to be changed within a moment's notice. However, how can this be done while still ensuring prior functionality is intact? That is where unit testing comes to the rescue, specifically, using Jasmine with Karma for Angular — the dynamic duo of testing!

Throughout this article, we will discuss what unit testing is, how it is implemented in Angular and the best practices for testing in an agile environment. Before jumping into writing unit tests, let's briefly discuss what they are and why they are needed.

What is Unit Testing?

Businesses often must redesign their websites and update the associated business logic to remain current. Nobody likes the idea of changing working software, which can be time-consuming and risky. Everybody is familiar with that time a developer needs to make a small change, which becomes a production nightmare. Unit tests mitigate these risks by constantly verifying code written against existing tests.

A unit test is an automated piece of code that tests individual methods of a given application. In other words, any portion of business logic that is needed to perform a particular function. For example, let's say a website has a checkout cart, and it needs to calculate everything inside and add sales tax. This logic would be done by writing a method, and a unit test would test against that particular method.

Due to the ever-expanding role of the front-end in software development, it is crucial to have plenty of unit tests to complement its functionality. With regards to Angular, these are all created using the Jasmine framework and executed using Jasmine. Let's take a look at how to write unit tests in Jasmine for an Angular application.

How To Unit Test in Angular Using Jasmine

Returning to our previous example, let's write a function that takes a list of item prices, a tax rate and calculates the total. (This is standard business logic that a shopping cart would use on any given website.) That code would look something like this:

calculateTotalWithTax(taxRate: number, …itemPrices: number[]): number {
let total = 0;
for (const item of itemPrices) {
   total += item;
}
total = total * taxRate;
return total;
}

So what this piece of code is doing is taking a given tax rate, say 6% in the state of Indiana — and a list of item prices. It is then iterating over those item prices and calculating the total. Lastly, it is applying the sales tax. A Jasmine unit test would look like this:

it(‘should calculate the total and add sales tax.’, () => {
   expect(component.calculateTotalWithTax(1.06, 4, 4, 4)).toBe(12.72);
});

There is a caveat here that a LOT of boilerplate code is behind this unit test. Luckily, it is all generated by the Angular application itself. All of the code for this walkthrough is available here. Remember, if you want to run an individual unit test, hit the play button next to the spec file's unit test. In this case, the spec file is called app.component.spec.ts.

A lot is going on in this unit test, so let's break it down a bit. First off, it is calling a method called it. It first takes a certain expectation. This expectation is what we would like the particular piece of code to do. In this case, the code will calculate a total and add the sales tax, so that is what we can describe in the expectation. The second parameter of the It function is the assertion. The assertion is a call back function that takes a function called expect. This is where the rubber meets the road. The expect function takes the method to be tested as a parameter, and then an expectation is invoked on that. It may seem complicated, but after enough practice, it will become second nature.

In the case above, the unit test invokes the function we created called calculateTotalWithTax, which takes two parameters: taxRate and itemPrices. We are calculating that number to be 12.72, which is why toBe method is called with that exact number. This is what's returned when that unit test is executed:

TESTS PASSED: 1 OF 1 TESTS

Great! Our test worked.

However, a new requirement has come down the pipeline. Because this is an Agile environment, it must be implemented as quickly as possible. The requirement is from the senior software developer, and he would like all for loops replaced with the "reduce" function. This will mitigate side effects and keep the code looking uniform. Sounds easy enough — the refactored code will look something like this:

calculateTotalWithTax(taxRate, …itemPrices): number {
   let total = 0;
   itemPrices.reduce((itemPrice, nextItemPrice) => {
         return itemPrice + nextItemPrice;
      });
   total = total * taxRate;
   return total;
}

Done! The for loop has been replaced with a reduce function. Push it to prod and get on with our day. Unfortunately, the programmer failed to see a very critical yet subtle error in this code. It is only revealed when the developer runs their Jasmine test. The Jasmine test tells him:

Error: Expected 0 to be 12.72.

Uh-oh, the calculateTotalWithTax is returning 0 when it should be returning 12.72! That is because they forgot to assign the total variable to the reduce function. If this were pushed to production, it could easily cost the company an incalculable sum. If it weren't caught for a week, who knows how much money they would miss out on because of a glitched shopping cart?

A simple unit test saved the company a lot of time, money, and reputation. After a little head-scratching, the developer changed the code to look like this:

calculateTotalWithTax(taxRate, …itemPrices): number {
   let total = itemPrices.reduce((itemPrice, nextItemPrice) => {
      return itemPrice + nextItemPrice;
   });
   total = total * taxRate;
   return total;
}

Then further refactored to this, removing the variable entirely:

a

calculateTotalWithTax(taxRate, …itemPrices): number {
   return itemPrices.reduce((a, b) => {
      return a + b;
   }) * taxRate;
}

Ramp-Up Time Reduction

The ability to perform regression tests on existing code is probably the essential part of unit testing, as demonstrated above. However, there are several great reasons for doing so. Let's touch on one in particular: reducing ramp-up time via documentation.

The first reason is that it can serve as the code's documentation. After all, IT jobs are 90% information and 10% technology. When onboarding a new developer, one of the most time-consuming aspects is ramping him or her up to the project at hand. Looking back at the Jasmine framework, you may notice that the code is unit tested in an easy-to-predict manner.

For instance, the unit tests always start with the format: it should do something, and this is the expectation. For a new developer, this should give them an easily organized and concise description of the existing production code. Also, unlike convention comments, the unit tests are sure to be updated every time the business logic itself is updated; so there will be no fear of out-of-date comments muddying the waters.

Final Thoughts

Unit testing, and testing in general, is an integral part of the software development process. You can even use GitHub to help manage automation. The wheels of any complex project will quickly fall off without it.  Even though this post only scratches the surface of unit testing, it hopefully conveyed the process's value and sparked an interest in pursuing the importance of testing even more.

Download

Download

Ultimate Networking Cert Guide

A 97-page guide to every Cisco, Juniper, F5, and NetApp certification, and how they fit into your career.

By submitting this form you agree that you have read, understood, and are able to consent to our privacy policy.

LEARNING ON MOBILE

Learn anytime anywhere with our mobile apps.

I have read and understood the privacy policy and am able to consent to it.

© 2021 CBT Nuggets. All rights reserved. Terms | Privacy Policy | Accessibility | Sitemap | 1550 Valley River Drive, Eugene, OR 97401 | 541-284-5522
CBT Nuggets