Ever have the feeling that you have written some code that may or may not work some of the time? …wait, what?
Gustavo: “Oh Alex, what did you work on today?” Me: “I created this new angular service that ensures only one dropdown is open at a time. It’s awesome!” Me: “Come check it out.”
Gustavo: “Wait, so it makes sure that there can be two open at the same time?” Me: “crap”
That’s why we write tests…even on the front-end. Maybe you currently don’t test front-end functionality in your app, maybe you dont test your server-side code, maybe you hate testing in general.Well… try Karma dude (it’s good for the soul)!
Let’s break down an example of testing in Angular, showing how Karma can put a smile on your face.
First things first: Install Karma Using npm
--save-dev flag above adds the Karma package to your devDependencies in your package.json file. This indicates to other developers that Karma is needed in order to develop this project.
Karma works by launching a browser, loading our app into the browser, and running our tests against our app…therefore, we need to tell Karma about all of our files.
Generate the configuration file and fill in each section using:
The config file that is generated will probably look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
angular-mocks.js and the rest of our angular code needs to be available to reference inside the
Note: since we have set
autoWatch: true, anytime we edit/change a file, Karma will automatically re-run our test-suite. Talk about instantaneous feedback…
Now we can run Karma by entering the follow line in the command-line:
The Code We Want to Test
According to our config file above, we will be using Mocha as our testing framework. Just a quick side-note…when it comes to front-end development, I typically go in with a strategy, write my code, and test after. Many times, I find that I make front-end implementation decisions on the fly, allowing for channeled spontaneity and creativity. Some may argue for TDD here, but because front-end developement requires an interation between functionality and design, I find that I am more productive writing my tests after implementation. So jump, take a dive… …and we’re in.
As you probably well know, Angular controllers are used to add functionality & data & information & behavior to the Angular Scope, which of course is used in the view. That being said, let’s think for a second how we might test an individual controller that is associated with a particular scope. Since Angular encourages, if not forces us to write modular code, we can certainly use this to our benefit in our tests. THIS IS THE BEST PART! We can actually test our controllers (and other angular components for that matter) in isolation!! So maybe we can somehow create comething called ‘scope’ in our test and give it some properties that mimic what the actual scope in our app might look like. Then, all we need to do is inject our controller into the test and hand it the mimicked scope that we just created. The controller is free to add/subtract whatever it needs or wants to from this scope object and we can test to make sure it is doing that correctly. Make sense?
If not, that’s okay. Let’s take a look…
For our example, lets consider a student database app with the ability to view all students in 1st, 2nd, or 3rd grade.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
- We start off with some grades to choose from.
- Since we have not yet selected a grade, our selectedStudents object is an empty array.
- We also have our allStudents array which will hold our index of students that returns from our call to the server at the bottom of the controller.
- Our functions defined on scope allow us to select/deselect a grade and set our selected students accordingly.
Testing Our Controller
Back to our tests, we will want to create a scope object to mimic the actual scope, allowing us to test the interaction of our controller with it. Let’s check it out. Be sure to notice the easy to understand syntax that Mocha provides!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
Let’s take this line-by-line and talk through exactly what is happening in our test. Before we start, notice the readability of the syntax here. Without further explanation, you already have a good idea of what we are testing.
After defining our test by giving it a name that describes what we are testing, we define a few variables that will be used later in our test. Of course, we need to load in the app that we are testing, so the beforeEach block helps us to to do this before running each test.
Now the fun stuff. This is key in understanding how testing in Angular is possible, so let’s take it slow and steady.
Earlier, I talked about creating a scope that mimics what our actual scope might look like. Thats where $rootScope comes in. If you are thinking, “whoa, we are actually creating a real, live scope here to use in our tests”, then you are correct. We are creating a brand new scope (with no parents or children since it is root). We are able to take that scope and give it to ‘MyController’ using the $controller service. An instance of ‘MyController’ is created and is now associated with this brand new scope that we just defined. Pretty cool, right?
Second part…we have this thing called $httpBackend. Angular gives this to us. It is a fake http backend implementation. Well, what the hell are we going to do with it? Good question. Again, Angular testing is awesome because we can break our code down into modules with no external dependencies. Therefore, putting the fate of our ‘MyController’ controller that knows nothing about some server millions of miles away seems to be a little out of our scope (no pun intended). What if the server is malfunctioning? What if it is configured wrong? The answer is that it doesn’t matter because $httpBackend allows us to create a mock of that server, completely seperating our controller’s concerns. In our example, we are using $httpBackend to simulate a successful server response. We define this response to be a JSON object with a ‘students’ property.
**Important: $httpBackend is also used in production. The $httpBackend service that we are using here is a mock. To elaborate further, the $httpBackend used in production responds to server requests asynchronously. In order to preserve the asynchronous nature of what is happening in production, we use $httpBackend.flush(). This allows us to make our simulated request and then receive the response when this method is called.
We also have defined an afterEach block. Here we are making sure that after each test all of our simulated server requests have been made and that none of our outstanding requests need to be flushed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Nothing too fancy here. We are testing that each property defined on scope is defined correctly upon controller initialization. Notice our test for students returned from the server. The array inside the allStudents property is the same array we defined as our response in the mock http request. Since we flushed the request, the response data is at our disposal.
Selecting a Grade Test
1 2 3 4 5 6 7 8 9 10 11 12 13
Again, we are being very descriptive of what exactly we are testing. In our beforeEach block, we are calling the selectGrade function that we defined on scope in our controller and passing in “third” grade. From here, we expect two different things to happen. Number one, selectedGrade is set to “third” and the correct students are set to the selectedStudents property. This is the beauty of angular testing. Since we took our time in setting up the infrastructure for our tests, we are able to write straight-forward expectations.
Deselecting a Grade Test
1 2 3 4 5 6 7 8 9 10 11 12 13
Here we are testing the de-selection functionality previously defined in our controller. Remember before how we set
autoWatch: true in our Karma config file? Again, this means that if we change any file that has been loaded into Karma (production code or tests), our test suite will automatically re-run.
I’d just like to leave you with a protip that I use A LOT, especially in my larger applications. Sometimes running your entire test suite in Karma can take a while and be unnecessarily repetitive. For instance, if you have a couple hundred tests written, but are only working on one specific component, maybe you don’t need to run your entire test suite every time you make a change. I am pleased to introduce
.only. This allows you to only run a specific file (if placed at the top of your test) or a specific test (if placed before a specific test), saving you lots of time. Have a look…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Only this file will run in the Karma test suite.
1 2 3 4 5 6 7
Only the tests nested under this describe block will run.
BE CAREFUL: about committing to your git repository without removing
.only. If you run your committed code in a test runner before merging with production, all of your tests may not run. So remove the
.only before committing!
Honestly, using Karma with Angular has delighted me to the fullest. Say what you want, but seeing those bright green checkmarks appear in front of me really keeps my stamina up, not to mention my entire day is brighter knowing that I am writing working, maintainable code. (Sometimes, I actually look more forward to writing tests more so than writing the actual implementation). Anyways cover your bases, test the front-end of your application, and have fun doing it!
…besides it’s good karma.