This post will go through how to write unit tests for your AngularJS app using Jasmine, how to run those tests in Visual Studio with ReSharper and the PhantomJS headless browser, and how to add a build step to your TeamCity CI pipeline to run the tests. The source code for the demo app is available in over on GitHub.
For this demo we will create a simple app with a controller and a service, and modules to add them to.
First, create modules for your controllers and services:
And create an app to host the modules:
Now we’ll create a basic service and add it to our services module:
And a basic controller into which we’ll inject our service:
Writing AngularJS unit tests with Jasmine
Jasmine is a BDD (Behaviour Driven Development) framework and is the framework chosen by the Angular team for use in their documentation samples. Other frameworks could be used but Jasmine provides the functionality that we require.
The first thing we need to do is add Jasmine to our project. Again, you can do this either by downloading the zip package and adding the scripts manually, or do it via NuGet. We will need the
jasmine-html.js scripts, plus the
boot.js script if you are using v2.0+. Also include
jasmine.css as content as we will use them later.
The introduction page on Jasmine’s website provides a good overview of how to write a test suite. The basics though are that you can use
describe to name your suite and pass in a function which contains your specs, and then use
it to name your specs and pass in a function which is the code to execute. You can nest test suites, and can use
beforeEach to execute a piece of code before each spec in that test suite.
Let’s first write some tests for our service. We’ll create
servicesSpecs.js and describe a suite for all our services, and nest a suite for our specific service:
We use the module function from angular-mocks in the
beforeEach hook to register the module configuration code. This allows us to call the inject function and use
$injector to instantiate an instance of our service. With our service instantiated, we can then write our expectations against the service.
You’ll notice the list of reference paths at the top of the file. It’s useful (and necessary for ReSharper later) to include references in your specs files for
angular-mocks.js, as well as the necessary application scripts for the test – this will give you IntelliSense in Visual Studio.
Next we can write some tests for our controller. Again, we’ll create
controllerSpecs.js, describe a suite for all controllers, and nest a suite for our specific controller:
Similar to before, we call the module function to register our controllers module. This time though, to instantiate our controller to test, we use the
$controller provider, where the first parameter is the name of the controller to instantiate, and the second parameter is the injection locals. Also, our
scope variable can be created by calling the
$new() function on
Running tests with ReSharper and PhantomJS
Now that we have some unit tests, we need a way of running them. One way of doing this is by configuring ReSharper to use PhantomJS to run them. You can also configure ReSharper to use the browser, but PhantomJS is headless, so will not pop open a browser window every time you want to run your test suite.
Now you should be able to see your tests in the ReSharper Unit Test Explorer window:
ReSharper uses the references specified at the top of your spec files (see earlier) to know which scripts to load so it’s important you include them. With these included, your tests should now run:
Running tests in a TeamCity CI build with PhantomJS
The final thing you may want to do is include your Angular test suite as part of your Continuous Integration pipeline in TeamCity.
To do this, we first need to create a SpecRunner.html (or name it whatever you like) file which will host our test scripts.
In the <head> of the HTML file we include the Jasmine CSS and JS scripts, Angular JS scripts, and our app and test JS scripts. If you are using Jasmine v2.0+, then you can include boot.js which contains the necessary initialisation for Jasmine. If you are using v1.3, then you need to include the following script:
You can test if your SpecRunner.html file is setup correctly by opening it in a browser. If it is, you should see the results from your test suite:
We can now use PhantomJS to load this HTML page and run the tests.
PhantomJS provides a run-jasmine.js script which writes out console messages based on the results from loading one of these SpecRunner HTML pages. Two GitHub users (dlidstrom and barahilia) have also usefully modified the script to output TeamCity service messages. If you are using Jasmine v1.3, grab this version; if you are using Jasmine v2.0+, grab this version. Include the script in your source code and then we can use it in TeamCity.
Test run the setup locally by running the following command (replacing with full file paths as necessary):
phantomjs.exe run-jasmine.js SpecsRunner.html
If it worked, you should see the following result:
If you get an error, be aware of two things that caught me out:
- Replace all backslashes
\with forward slashes
/in the path to SpecsRunner.html, otherwise it does not resolve the script paths correctly.
- Ensure SpecsRunner.html is saved in UTF-8 encoding. I had issues with an HTML file I created in Visual Studio and had to re-save it in UTF-8 using Sublime Text.
With all that working, all we have to do is add a build step to our configuration in TeamCity and use a command line runner to execute the command. You can either download the PhantomJS executable on your build agent, or include it in your source code and reference it from there.
When you run the build, the test results should automatically get added to the Tests tab (thanks to the service messages):
And now you have unit tests for your AngularJS app built into your TeamCity CI pipeline.