Unit testing AngularJS with Visual Studio, ReSharper and TeamCity
Setup
In your Visual Studio project, add the JavaScript files for AngularJS (this can be done either by downloading the package and manually adding the scripts or, more simply, via NuGet). As a minimum, we need the angular.js
and angular-mocks.js
scripts.
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.js
and jasmine-html.js
scripts, plus the boot.js
script if you are using v2.0+. Also include jasmine_favicon.png
and 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 jasmine.js
, angular.js
and 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 $rootScope
.
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.
To configure ReSharper to run the tests, firstly you will need to download the PhantomJS executable and place this on your local drive somewhere. Then, in Visual Studio, go to the ReSharper options and navigate to Tools -> Unit Testing -> JavaScript Tests. Ensure 'Enable Jasmine support' is ticked, set the correct Jasmine version, and browse for your PhantomJS executable.
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.
**EDIT: **Since writing this post, Howard van Rooijen has created a TeamCity metarunner which bundles up the PhantomJS executable and the run-jasmine scripts. Details are over on his blog post.