Join us in our way to learning Angular.js

Introduction to Unit Test: Introduction

What is unit testing and why should I care?

Unit tests are a bunch of Javascript files that we create to make sure that every part of our application works as it is expected to work. That means that we need to write hundred of lines of code to assert that our code does what is supposed to do.

Isn’t that a waste of time? The boss is always telling us that we need to be faster and hundred of lines doesn’t sound like fast. Au contraire, that bunch of code will save us HOURS. Don’t believe me? I have proofs.

Extra code: How many times did you end with code that is not used? Maybe we added some extra loops that are not needed or some function to do something and then realize that we are not using it. When we code our modules before any test, we don’t actually know what we are going to need or if our algorithm is going to support any kind of input (that could lead to those extra loops). More code means more stuff to maintain which also means, more money.

Bad API design: Maybe we need to create a new service to do something, and then we start writing functions to do the work and we put some of them public to define the service’s API. Good, that is the idea isn’t it? Some time after we get complaints about our really poor API that well, it is not as intuitive as we expected. In this category also goes those API functions that are not really needed (which is also extra code).

Refactor: What happens when we want to refactor our code? We are in big trouble. Even when we decide to not break the API, maybe that internal change is not working properly on some edge cases where it worked in the past. That will break the application for some people and they won’t be happy at all (and those kind of bugs are normally a pain in the ass).

Will it work: That is the end goal and probably the biggest time waster of anything you have to do in your applicaton. Something as simple as a calendar, involves some maths and some magic numbers to make it work. We really need to be sure it works. How? We open a certain date, we manually check with our OS calendar to see if it matches. We repeat that for some random dates (old ones, future ones). Then we change something in our service and well, we need to check the dates again to assert that nothing is broken. Repeat that 20 times for a normal service development.

How does the unit test help?

Ok, you convinced me that maybe I was wrong about not doing unit testing. But how can it help with those problems? What if we see a really simple example? (General example, not angular related and it will be in a overly slow peace to make the point).

Let’s say I want an object which will be able to do some basic maths (Addition, Subtraction, Multiplication, Division) and your first thought will be to start writing a constructor with some prototype functions to do some math. We will end doing something like that, but what we are going to do is to test it first. Test it first? Why? Bear with me.

describe('Calculator',function(){varcalc;beforeEach(function(){calc=newCalculator();});describe('Addition',function(){it('should be able to sum 5 and 3 to return 8',function(){varresult=calc.addition(5,3);expect(result).toBe(8);});});});

If we put that on a spec file and run it we get:

It says that it can’t create a new Calculator and it is not able to do that addition (surprise!). Well, we have no code. Before continuing, I am going to explain how jasmine tests work.

Jasmine is like writing English. It is something easy to read and understand (which is waay cool). Jasmine spec files are normally wrapped on a describe block which receives a string to define what are we describing. They are used to group tests. We can see how we have another describe block which is nested in the previous one with the addition as parameter. See how are we grouping the tests?

What we need to do to write tests is to use the it function. It receives the name of the test and a callback function that will contain the test itself. In this case it is testing that what we get from the addition function is the correct value. What about that beforeEach? Since we need to create a calculator in all the tests, we can just create one to not repeat ourselves. Read with me: before each test create a new calculator.

See how easy is to make a test. We use the expect function were we pass our result and then the toBe jasmine function which receives the expected value. Read with me: expect result to be 8.

There is an important concept in unit testing. Every test in independent of each other, that means that every test will start with a fresh state (In our case, a new Calculator object).

Are you starting to see what we are getting here so far? API design. By using our object before we coded it, we are using the API as we would like to use it. That is a much much better way to define our API.

Yes it does! This is an example of no extra code. We coded the minimum necessary to make it work, and well, that is what we need at this point.

Of course, we are not finished yet with our tests. We want to know if we can sum 7 and 0. We test it on a new it function:

12345678

describe('addition',function(){// earlier test hiddenit('should be able to sum a number with 0',function(){varresult=calc.addition(7,0);expect(result).toBe(7);});});

Well, that fails, and we know why. For the sake of learning we are going to do an extra step to fix it:

calculator.js

123

Calculator.prototype.addition=function(num1,num2){return7+0;};

Ups, we broke the last test. That is wonderful. That solves our Will it work? problem. We can immediately see that we broke our code when we modified our function to pass the new test.

Let’s fix it for once:

calculator.js

123

Calculator.prototype.addition=function(num1,num2){returnnum1+num2;};

Uh, finally. Now we have a proper addition method which just the needed code to make it work, no extra params either. We can add some more tests (to the addition describe):

calculator_spec.js

123456789

it('should be able to sum a negative number with a positive result',function(){varresult=calc.addition(7,-3);expect(result).toBe(4);});it('should be able to sum a negative number with a negative result',function(){varresult=calc.addition(-20,7);expect(result).toBe(-13);});

Uh, it works without any extra code! Better for us. Let’s do the division:

calculator_spec.js

123456

describe('division',function(){it('should be able to do a exact division',function(){varresult=calc.division(20,2);expect(result).toBe(10);});});

We see it fails, it doesn’t have that division method.

calculator.js

123

Calculator.prototype.division=function(num1,num2){returnnum1/num2;};

We are smart enough to make a proper first version which actually passes.

Now, for non exact divisions, we want to round the result, we don’t want any decimals.

calculator_spec.js

1234

it('returns a rounded result for a non exact division',function(){varresult=calc.division(20,3);expect(result).toBe(7);});

Our current implementation is not rounding the result at all. Let’s fix that:

This time we didn’t break our last implementation, that is something :P

What about throwing an exception if we divide something by 0? Sure:

calculator_spec.js

12345

it('should throw an exception if we divide by 0',function(){expect(function(){calc.division(5,0);}).toThrow(newError('Calculator does not allow division by 0'));});

This test is a little bit different. Instead of passing a variable to expect we are passing a function. We expect that call to end on an exception so saving the result as we previously did won’t work (We expect to never return that result but throw an exception). We also use the toThrow function on Jasmine.

It fails, we are not throwing any exception yet. Let’s fix that:

calculator.js

123456

Calculator.prototype.division=function(num1,num2){if(num2===0){thrownewError('Calculator does not allow division by 0');}returnMath.round(num1/num2);};

Calculator.prototype.division=function(dividend,divisor){if(divisor===0){thrownewError('Calculator does not allow division by 0');}returnMath.round(dividend/divisor);};

Uh, we did a refactor and we didn’t break anything.

I will leave the other two calculator functions as an exercise.

Conclusions of this example

Even when it is really really simple example. We already saw how we can address those problems I described earlier:

Our calculator doesn’t have any extra code because we coded just what we needed to make our calculator work. Its API design is good enough, that is because we used it as we would like to use it on the real world. Will it work? Sure, I have a bunch of tests that proves that. What about refactor? Go ahead, if the tests still pass, then you’re doing good.

Maybe you won’t notice it with this example, but with proper tests, you will save a lot of hours maintaining extra code, dealing with API design with hopefully won’t end on breaking changes, refactoring code without fear and of course being sure that your code will work.

Testing is your friend, and with little effort on it, will save us real pain.

How can we test Angular.js?

Surprisingly enough, almost the same as our calculator. Since Angular.js is more complex than basic Javascript it involves a little more of work.

There is a couple of things to learn. How to work with angular modules and dependency injection. They don’t work as we are used to, they need some special ways that already exists on a file called angular-mocks.js. It is better if we see a skeleton:

It should be very familiar now, but there are some Angular bits. First of all, my convention is to put what are we testing and the name, for example: controller: foo. Then we need to load all the modules involved, if it is just app, we just need to load that one. To load it and since we need it before each test, we use the module function (from angular-mocks) to load that app module for us. If we have more dependencies like ngRoute, we also need to load it (before app).

The other different part is how we inject stuff. To inject we need to use the inject function (from angular-mocks) which will receive a function with all the stuff we need to inject. What about those underscores? Angular ignores them, so we can use that to be able to have our local variables with the original service name.

The rest is not new. Once we have our modules loaded and our services injected and saved in local variables, we just need to do some testings. For that, we need to wait for the next articles of this series.