How can I test a directive using templateUrl in Chutzpah's headless browser

All we need is an easy explanation of the problem, so here it is.

Does anyone know how to get a headless browser like Chutzpah’s Visual Studio Test Adapter to allow a directive to access its .html template file? Chutzpah uses PhantomJS for a headless browser which appears to limit my options.

I’m using Chutzpah Test Adapter 2.5 and AngularJS 1.2.0-r.2.

I get the error:

Unexpected request: GET myApp/directives/template.html

Which is caused by Angular attempting to use the $http service to access my directive’s template.

I’ve found a few different workarounds:

  1. Manually using XMLHttpRequest to import the templates.
  2. Using a utility like Grunt to inline the template into your directive’s JS code.
  3. $httpBackend.when('GET', 'myApp/directives/template.html').passThrough() – this only works in e2e tests, not unit tests.
  4. Put the template directly into the test code.

None of these options particularly satisfy me. I’d prefer to be able to let the directive load its template transparently so I can test it as a component. Is there a way I can get this scenario working?

Example code:

angular.module('myDirective', []).directive('myDirective', function() {
    return {
        restrict: 'E',
        transclude: true,
        templateUrl: 'myApp/directives/template.html',
        // Some other options, omitted for brevity.
    };
});

template.html:

<div><div ng-transclude></div></div>

Example Jasmine test:

describe('myDirective', function() {
    // Assign $scope using inject(), load module etc.
    // Insert workaround for $httpBackend loading my templateUrl here.
    it('should transclude content', function() {
        var element = $compile("<my-directive>Look Mom, I'm in my directive!</my-directive>")($scope);
        $scope.$digest();

        expect(element.text().trim()).toBe("Look Mom, I'm in my directive!");
    });
}

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I managed to get it working, using Chutzpah’s compile option. I ported karma-ng-html2js-preprocessor into a PowerShell script and then call that from Chutzpah to compile the HTML into JS files.

Added PowerShell script next to chutzpah.json settings file (the below script contains untested support for PreprendPrefix and StripSuffix)

# PowerShell port of https://github.com/karma-runner/karma-ng-html2js-preprocessor

Param (
    [parameter(Mandatory=$true)] [string] $Path,
    [string] $Module = '',
    [string] $StripPrefix = '',
    [string] $PrependPrefix = '',
    [string] $StripSuffix = ''
)

Function EscapeContent($content) {
    $content -replace "\\", "\\\\" -replace "'", "\'" -replace  "`r?`n", "\n' +`n    '"
}

Function Rename($fileName) {
    "$PrependPrefix" + ("$fileName" -replace "$StripPrefix", '' -replace "$StripSuffix", '' -replace "\\", "/")
}

$Template = If ("$Module" -eq "") {
    "angular.module('{0}', []).run(function(`$templateCache) {{`n" `
      + "  `$templateCache.put('{0}',`n    '{2}');`n" `
      + "}});`n"
  } Else {
    "(function(module) {{`n" `
        + "try {{`n" `
        + "  module = angular.module('{1}');`n" `
        + "}} catch (e) {{`n" `
        + "  module = angular.module('{1}', []);`n" `
        + "}}`n" `
        + "module.run(function(`$templateCache) {{`n" `
        + "  `$templateCache.put('{0}',`n    '{2}');`n" `
        + "}});`n" `
        + "}})();`n"
  }

Get-ChildItem $Path -Recurse -Filter *.html | Foreach-Object {
    $content = Get-Content $_.FullName | Out-String
    $content = EscapeContent $content
    $fileName = Rename "$($_.FullName)"
    ("$Template" -f "$fileName", "$Module", "$content") | Set-Content "$($_.FullName).js"
}

Added compile configuration to chutzpah.json and add the “compiled” JS files to References (the reference could added from the test file, but I prefer to use chutzpah.json to manage all the references)

{
    "References": [
        { "Path": "./path/to/templates/", "Include": "*.html.js" },
    ],
    "Compile": {
        "Extensions": [".html"],
        "ExtensionsWithNoOutput": [".html.js"],
        "SourceDirectory": "./path/to/templates",
        "OutDirectory": "./path/to/templates",
        "Executable": "%powershellexe%",
        "Arguments": "-NoProfile %chutzpahsettingsdir%\\chutzpah-ng-html2js-compiler.ps1 -Path '/path/to/templates/' -Module 'templates' -StripPrefix '.+(?=\\\\templates)'"
    }
}

In your test files, load the module, in my case they are all in ‘templates’ module

beforeEach(module('templates'));
// or alternatively
beforeEach(module('/path/to/templates/foo.html'));

Method 2

you can use the “template” Reference from Chutzpah

http://matthewmanela.com/blog/chutzpah-2-4-2/

/// <template path="../../MvcAngularJs1_3/ScriptsApp/directives/Templates/testTemplate.html" />
/// <reference path="../../mvcangularjs1_3/scripts/jquery-2.1.1.js" />
/// <reference path="../../mvcangularjs1_3/scripts/angular.js" />
/// <reference path="../../mvcangularjs1_3/scripts/angular-mocks.js" />
...

under the references, then Chutzpah includes the HTML directly in the Test HTML File. I’ve given my Templates a unique css Class to find them and put them by hand into the Template AngularJS Template Cache.

my HTML Template

<div class="row templateTestTemplate">
   <div class="col-lg-12">
       <h1>Tempalte Test</h1>
   </div>
</div>

the directive

angular.module("testTemplateDirective", [])
    .directive("testTemplate", function() {
        return {
           restrict: 'A',
           replace: true,
           templateUrl: 'ScriptsApp/directives/Templates/testTemplate.html'
       }
});

in my unit Tests I am using jQuery to find the Template and add it to the Template Cache. Angular firsts looks in the Template Cache and don’t makes an HTTP call.

var template = jQuery(".templateTestTemplate")[0].outerHTML;
$templateCache.put("ScriptsApp/directives/Templates/testTemplate.html", template);

that works Great so far, but only works with chutzpah, when you run this Test with Resharper the test will fail, because resharper does not copy the template into the Test HTML.

I’ve also opend a request on the Chutzpah GitHub Page to surround the HTML with Script Tags and an ID so that you don’t need to extend the Template
https://github.com/mmanela/chutzpah/issues/330

Method 3

I had a similar issue trying to test my directives. I found a solution using the pattern from
https://github.com/vojtajina/ng-directive-testing.

Step 1: use ng-html2js plugin to compile angular template into javascript
Step 2: do like the previous answered replied and include it as a module in your before each.

ng-html2js also allows you to perform custom manipulation of the ‘url’ that it expects which could be helpful if your test directory is not the same as your app directory.

Method 4

Using ng-html2js this is a great video that shows how to get it all setup to test the templateUrl directive. I am not sure how applicable this is to Chutzpah as this is a Karma example, but it might get ya in the right direction!

https://vimeo.com/90876119

Method 5

You can use jasmine-jquery to load content from file or http into $templateCache. It works in Chutzpah without any modifications.

First add jasmine-jquery to your project and add in chutzpah.json References:

(from my code)

"References" : [
    {"Path" : "vendor/jasmine-jquery-2.1.0.js"}
]

Then in your test, inject $templateCache and load it with “live” content via jasmine.getFixtures() like so:

beforeEach(inject(function ($templateCache) {
    //Loading template from live develop server
    jasmine.getFixtures().fixturesPath = "http://localhost:47152";
    var templateUrl = "/myApp/directives/template.html";
    $templateCache.put(templateUrl, jasmine.getFixtures()  
        .getFixtureHtml_(templateUrl));
}));

If you load via http and use Chrome for your tests, you must add CORS support on the server.

Method 6

Make sure you include your template into your injector before it gets created. So try,

beforeEach(module('myApp/directives/template.html');

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply