Let’s talk about unit testing our applications.
What is unit testing and why should I care?
- 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.
(If you want to follow this, I have a plunker for you to work.)
Our object should be able to sum
3 and get
8. Let’s test that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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
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
Let’s make that test pass:
1 2 3 4 5 6
Does it pass?
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
1 2 3 4 5 6 7 8
Well, that fails, and we know why. For the sake of learning we are going to do an extra step to fix it:
1 2 3
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:
1 2 3
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
1 2 3 4 5 6 7 8 9
Uh, it works without any extra code! Better for us. Let’s do the
1 2 3 4 5 6
We see it fails, it doesn’t have that
1 2 3
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.
1 2 3 4
Our current implementation is not rounding the result at all. Let’s fix that:
1 2 3
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:
1 2 3 4 5
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:
1 2 3 4 5 6
Well, we just need to check if
num2 is just 0 to throw the exception.
With that, we finished our
division method… Wait a second… Those parameter names suck. I agree, let’s change them:
1 2 3 4 5 6
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
There is a couple of things to learn. How to work with angular
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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
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.