AngularJs Unit Testing Part 1 – Controllers

As a developer coming from an ASP.Net, C# background, I had come to appreciate the ability to unit test my code. I find that I am not all that great a programmer, and often times the unit tests I write have brought to surface a bug that I would have introduced to production had I not had a unit test in place that discovered the bug.

In the past, I had not given much thought to do JavaScript unit tests. I had just assumed that it wasn’t really possible to do them without a lot of jumping through hoops. This, of course isn’t true, and with AngularJs, unit testing is pretty straight forward. I have found that once I have gotten over the initial hurdle of getting everything setup and understanding some of the quirks of testing AngularJs code, unit testing JavaScript code has become quite useful.

So with that being said, I am going to put together a series of posts on different aspects of testing AngularJs code from testing controllers, services and directive, to settings up mocks to simulate calls to an API.

The Application

So for this exercise, I am going to create a sample application called Student Portal. That will allow students to look at the courses they have taken. To start out, I will have a simple landing page, that shows some introductory content.

The Testing Framework

At Campus Nexus we use Jasmine for writing unit test but there are other good testing frameworks like Mocha and the Chai add-on for Mocha. You can set up a test run as part of your build using Karma, however Jasmine also has a pretty nifty set of scripts and CSS that you can add to an HTML page then run them from the browser.

All the scripts are in a CDN so you don’t even need to download anything with this approach. Here is the spec page I have.

 

The Home Controller

For my first test, I am going to put a title on the scope and test that it is actually there.

Using a TDD (test first approach),  I typically start testing by first creating a unit test that proves that my controller does indeed exist. To do this I cannot simply grab a reference to it, I need to use the $controller service provided by Angular. Angular has mocking framework called NgMock that takes the $controller service and decorates it with an extra bindings parameter. the $controller service provided by NgMock takes three parameters:

  1. The controller name of the existing controller.
  2. The second parameter takes an object of key names that the controller have dependencies on that you can inject into your controller. This allows you set the parameters you pass to the controller function.
  3. The last argument allows you to bind to the ‘this’ object of the controller itself.

Here is the code for the controller.

Currently, the controller does not do anything, because I only wrote enough code to get the test to pass so lets look at the unit test code.

I am using the Jasmine framework for testing.

The “describe” function, is way to organize you code. Think of it as a namespace for your unit tests. You can also have nested “describe” function to organize your tests even further.

The “beforeEach” function will run before every test in your particular testing scope. This function is useful for placing your reusable code instead of copying the same code into every test.

Finally the “it” function is where you define your tests. Typically, the description of each test should start with the “should” BDD nomenclature.

Angular’s NgMock service, has a way to inject services (both Angular services and your own services), that you can then use to create the functionality you need to get the specified controller. The “inject” function does this for you. Note: that to inject the services, I need and then assign them to module level variable. To do this, I change my service name by adding “_” to the front and back of the variable. in the “inject” parameter. Angular will ignore these characters but the JavaScript engine will think they are different variables and you will not get an exception.

So the above test passes, so now let’s test that I have property on the controller called title. First let create a test for this:

So this test fails, because I haven’t set the variable in the controller yet. If I add the following code in the controller:

Now the test passes.

Mocking a Dependent Service that Returns a Promise

Okay, now let’s do some a bit more complicated. My home page will make an API call to get a list of images and it will rotate through these images.

I am going to start with the test:

This test fails so I need to make it pass.  To do this I need to make changes to both the controller and the spec file.

First, though, I need to add some mock data because I do not want to make a real API call when I am test this functionality.

On the controller I am injecting a service that returns a list of images from the server. It’ important to note that I am testing something that is asynchronous which adds a bit of complexity to the test.

Here is my service that will make an HTTP call.

Okay, lets look at the test file because, here is it gets a bit tricky and there is a lot going on.

First lets focus on the “beforeEach” function because the steps here are important.

  • Since we don’t want to actually make an API call, we need to mock the service and function call. To do that, we can use Jasmine’s “createSpyObj” function. This function takes the name of the service as the first parameter and an array of functions as the second parameter.
  • Next we need to create a mock promise that when resolved will returned a mock response.
  • Then we can use Jasmine to spy on the “getImages” function, that when called, will return the promise.
  • Finally, (and it’s important that we do this last), we need to initiate the controller we are testing.

Then in the test we need to do the following:

  • First we need to resolve the promise just like you would with any promise functionality.
  • Now, if we run our test it will fail, because we resolved the promise, but angular does not know about it, so we need to angular to cycle though any scope changes. We can do this by using the “$apply” function on the scope.
  • Now we can check our scope to see if it got the correct data.

jasmine1

Conclusion

Unit testing can range from being a really simple task to pretty complex so hopefully this post will give you a reference on how to write your own tests.

I have put the project code up on Github.

 

Leave a Reply

Skip to toolbar