Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Doing angularjs on visualforce in a big way lately, using lots of ngforce.

In all the mainstream (ie, angular, there's some slick unit testing stuff. But it looks like the assumption is that you're always running nodejs underneath, or otherwise have a way to execute the code in test format.

Other than execution testing (ie, driving a browser via selenium) what's a good way to do angularjs (or javascript in general, really) unit tests when they live in a visualforce context?

share|improve this question
up vote 6 down vote accepted

ngForce author here.

I use Jasmine, and droneIO to run tests both in browser with a static spec runner and via PhantomJs. Our drone job auto-deploys to our testing org when the tests pass.

Our static, in org vf page for running the angular unit tests looks a bit like this:

<apex:page showHeader="false" sidebar="false" standardStylesheets="false" docType="html-5.0">         
        <!-- Include source files required to test application.
        - Testing Framework
        - App Dependencies (anything like chosen.js or jquery)
        - Testing Libraries (e.g. mocks)
        - App Source Files
        - App Test Files

And a typical Jasmine file looks something like this:

 * Unit test spec for searchService.js.
describe('The search service - guess what I do?', function() {

    // Test dependencies (not subject of tests)
    var $httpBackend,

    beforeEach(function() {
        Visualforce = getVisualforceMock();

    beforeEach(module('ourApp', function($provide) {}));

    beforeEach(inject(function($rootScope, _SearchService_, _$q_, _$httpBackend_) {

        SearchService = _SearchService_;
        $q = _$q_;
        sfrqueryBackend = _sfrqueryBackend_;
        $scope = $rootScope.$new();
        $httpBackend = _$httpBackend_;
        $httpBackend.when('GET', /apex\/OA\_/).respond({}); // Handle requests for VF partials.
        $httpBackend.when('GET', /query.*from\++plan__c/i).respond(200, {
            "totalSize": 1,
            "done": true,
            "records": [fakePlan]

    afterEach(function() {

    it('should query the search__c table', function() {
        // Control dependencies

        // Invoke functionality
        var soqlFrom = SearchService.from();

        // Compare actuals to expecteds
        expect(soqlFrom.indexOf("FROM search__c ")).toBeGreaterThan(-1);

Our setup utilizes some grunt tasks that we put together utilizing kevinOhara's grunt-ant-deploy and PhantomJS test running. Here's the relevant gruntfile.js bit for testing:

jasmine: {
            proposalPlus: {
                // Include paths for both the local user, if we're in dev mode, as well as share mode
                src: [
                    // ***Note well*** Order matters here.
                    // External JS libraries in play and Base Angular libs
                    // Application Specific JS
                    // Mocks
                options: {
                    // Include path for Jasmine spec files
                    // Only include those ending in Spec.js
                    // Specifically excluding those ending in NotReady.js
                    specs: [
                    version: '2.0.0',
                    // we utilize a code coverage tool called istanbul.
                    template: require('grunt-template-jasmine-istanbul'),
                    templateOptions: {
                        coverage: 'bin/coverage/coverage.json',
                        report: 'bin/coverage',
                        thresholds: {
                            lines: 80,
                            statements: 80,
                            branches: 80,
                            functions: 80

(that goes in grunt.config()) this is the task def we use:

grunt.registerTask('default', 'jasmine');
share|improve this answer

Karma tests are essentially JavaScript unit tests, so as long as you can get your app.js, controller.js etc files out into a file system you can run those tests manually and from e.g. Jenkins with just something like this:

karma start karma.conf.js --single-run --browsers Chrome

Karma does use a browser, but is amazingly fast (milliseconds per test) and takes care of all the messy stuff for you - you just write (Jasmine) tests and it runs them. The test JavaScript files and test configuration can be kept in a separate SVN project.

I also have Protractor (end to end) tests running against my Angular app deployed in a test org again using Jasmine for the tests (with page objects). The most awkward thing there was to get the org into a defined state at the start of a test run and to do that I added a custom Apex REST class that does all the work that can be called using a few lines of JavaScript (making use of the "request" Node package). Again kicking it off is simple:

protractor protractor.conf.js --browser chrome

So bottom line, the tests and test framework setup are all in the Node/Npm world where it is all JavaScript. For the unit tests you need to add in your .js files, and for end to end testing you need a dedicated deployed (to Salesforce) instance of your app to test against.

I wrote JavaScript testing blog post a while ago on the subject; what it doesn't say is that it takes a few days to get up the learning curve for both Karma and Protractor. And there is also Jasmine and Selenium grid to learn about. So if this is all new, figure a couple of weeks to retrofit a reasonable level of unit and end to end tests assuming your app is well structured. If the app requires refactoring to make it testable figure longer...

PS ("Life's too short for long boring lectures") have a couple of dozen 5 minute videos on Angular testing using both Karma and Protractor. Here is a (free) example: Testing Overview.

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.