【问题标题】:Unit testing the output of $sce.trustAsHtml in Angular在 Angular 中对 $sce.trustAsHtml 的输出进行单元测试
【发布时间】:2014-01-23 15:32:20
【问题描述】:

我正在用 Angular 编写一个 REST 应用程序,我想为它编写单元测试(当然!)。我有一个控制器,它从 json 中的 REST 服务获取博客文章列表,并将摘要放入 $scope,以便我可以在视图中显示它们。

起初,博客文章只是显示为文本,即<p>Blog body</p>,而不是呈现为解析的HTML,直到我发现您可以将ng-bind-html$sce service 结合使用。现在,这在正确显示博客文章方面效果很好。

单元测试时出现问题。我正在尝试使用一些 HTML 模拟 json 响应,然后测试我的控制器是否正确处理 HTML。这是我的代码:

控制器

.controller( 'HomeCtrl', function HomeController( $scope, $http, $sce ) {
    $scope.posts = {};
    $http.get('../drupal/node.json').success(function (data) {
        var posts;
        posts = data.list;

        for(var i = 0; i < posts.length; i ++) {
            posts[i].previewText = $sce.trustAsHtml(posts[i].body.summary);
            posts[i].created = posts[i].created + '000'; // add milliseconds so it can be properly formatted 
        }
        $scope.posts = posts;
    });
})

单元测试

describe('HomeCtrl', function() {
    var $httpBackend, $rootScope, $sce, createController;

    beforeEach(inject(function ($injector) {
        // Set up the mock http service responses
        $httpBackend = $injector.get('$httpBackend');

        // Get hold of a scope (i.e. the root scope)
        $rootScope = $injector.get('$rootScope');
        // The $controller service is used to create instances of controllers
        var $controller = $injector.get('$controller');

        $sce = $injector.get('$sce');

        createController = function() {
            return $controller('HomeCtrl', {
                '$scope': $rootScope
            });
        };
    }));

    it('should get a list of blog posts', function() {
        var rawResponse = {
            "list": [
                {
                    "body": {
                        "value": "\u003Cp\u003EPost body.\u003C\/p\u003E\n",
                        "summary": "\u003Cp\u003ESummary.\u003C\/p\u003E\n"
                    },
                    "created": "1388415860"
                }
            ]};
        var processedResponse = [{
                "body": {
                    "value": "\u003Cp\u003EPost body.\u003C\/p\u003E\n",
                    "summary": "\u003Cp\u003ESummary.\u003C\/p\u003E\n"
                },
                "created": "1388415860000",
            previewText: $sce.trustAsHtml("\u003Cp\u003ESummary.\u003C\/p\u003E\n")
        }];

        $httpBackend.when('GET', '../drupal/node.json').respond(rawResponse);
        $httpBackend.expectGET("../drupal/node.json").respond(rawResponse);
        var homeCtrl = createController();
        expect(homeCtrl).toBeTruthy();
        $httpBackend.flush();
        expect($rootScope.posts).toEqual(processedResponse);
    });
});

当我通过 Karma 测试运行器运行上述内容时,我得到以下响应:

Chrome 31.0.1650 (Windows) home section HomeCtrl should get a list of blog posts FAILED
    Expected [ { body : { value : '<p>Post body.</p>
    ', summary : '<p>Summary.</p>
    ' }, created : '1388415860000', previewText : { $$unwrapTrustedValue : Function } }          ] to equal [ { body
: { value : '<p>Post body.</p>
    ', summary : '<p>Summary.</p>
    ' }, created : '1388415860000', previewText : { $$unwrapTrustedValue : Function } }     ].

我怀疑问题是由于$sce.trustAsHtml 返回一个包含函数的对象,而不是字符串。

我的问题是,首先,我是否以正确的方式处理这个问题?

其次,如果是这样,我应该如何测试$sce.trustAsHtml 的输出?

【问题讨论】:

    标签: unit-testing angularjs jasmine karma-runner


    【解决方案1】:

    由于 michael-bromley 给出的答案对我不起作用,我想指出另一种解决方案。在我的例子中,我使用了一个过滤器,它将每个字符串的出现都包装在另一个字符串中,该字符串的跨度为“highlight”类。换句话说,我希望单词被突出显示。代码如下:

    angular.module('myModule').filter('highlight', function ($sce) {
        return function (input, str) {
            return $sce.trustAsHtml((input || '').replace(new RegExp(str, 'gi'), '<span class=\"highlighted\">$&</span>'));
        };
    });
    

    我使用 $sce 服务将结果值信任为 HTML。为了测试这一点,我需要对结果值使用 $$unwrapTrustedValue 函数来让我的测试工作:

    it('01: should add a span with class \'highlight\' around each mathing string.', inject(function ($filter) {
        // Execute
        var result = $filter('highlight')('this str contains a str that will be a highlighted str.', 'str');
    
        // Test
        expect(result.$$unwrapTrustedValue()).toEqual('this <span class="highlighted">str</span> contains a <span class="highlighted">str</span> that will be a highlighted <span class="highlighted">str</span>.'); 
    }));
    

    更新:

    正如@gugol 所指出的,最好不要使用像 $$unwrapTrustedValue 这样的 Angular 内部方法。更好的方法是在 $sce 服务上使用公共 getTrustedHtml 方法。像这样:

    it('01: should add a span with class \'highlight\' around each mathing string.', inject(function ($sce, $filter) {
        // Execute
        var result = $filter('highlight')('this str contains a str that will be a highlighted str.', 'str');
    
        // Test
        expect($sce.getTrustedHtml(result)).toEqual('this <span class="highlighted">str</span> contains a <span class="highlighted">str</span> that will be a highlighted <span class="highlighted">str</span>.');
    }));
    

    【讨论】:

    • 这似乎是一种非常干净的方法,并且选择的答案对我不起作用,但这个答案对我有用。 ;)
    • 看起来这个答案是更好的答案,所以我会相应地标记它。
    • 虽然此方法有效,但不建议使用以 $$ 为前缀的属性/函数。您正在使用未记录的并且容易更改 angular 的内部功能。
    • expect($sce.getTrustedHtml(result).toEqual('this &lt;span class="highlighted"&gt;str&lt;/span&gt; contains a &lt;span class="highlighted"&gt;str&lt;/span&gt; that will be a highlighted &lt;span class="highlighted"&gt;str&lt;/span&gt;.');
    • 感谢 Angular 文档人员突出显示 $sce.trustAsHtml()(它返回一个对象)而不是 $sce.getTrustedHtml()。感谢您的回答,它也对我有用。
    【解决方案2】:

    您必须在每次测试之前使用它的提供者禁用 $sce。

    当 $sce 被禁用时,所有 $sce.trust* 方法只返回原始值而不是包装函数。

    beforeEach(module(function ($sceProvider) {
      $sceProvider.enabled(false);
    }));
    
    it('shall pass', inject(function($sce){
      expect($sce.trustAsHtml('<span>text</span>')).toBe('<span>text</span>');
    }));
    

    在您的特定示例中,只需执行以下操作:

    describe('HomeCtrl', function() {
      var $httpBackend, $rootScope, $sce, createController;
    
      beforeEach(module(function ($sceProvider) {
        $sceProvider.enabled(false);
      }));
    
      // rest of the file
    });
    

    【讨论】:

    • 这应该是公认的答案。我将此添加到我的失败测试中,突然间我所有的测试都通过了。
    • 我的测试因“错误:[$sce:unsafe] 尝试在安全上下文中使用不安全值而失败。”即使实际实施没有问题。这个答案让我可以继续按预期使用框架,而无需更改实现或使用 Angular 的未记录/私有内部结构。这应该是公认的答案。
    【解决方案3】:

    我发现您可以使用$sce.getTrusted,它将返回最初传递给$sce.trustAsHtml 的值,在本例中是一个包含HTML 的字符串,然后您可以用通常的方式测试它是否相等。

    所以我的测试现在看起来像这样:

    it('should create a previewText property using $sce.trustAsHtml', function() {
        // confirms that it is an object, as should be the case when 
        // it has been through $sce.trustAsHtml
        expect(typeof result.previewText === 'object').toEqual(true);
    
        expect($sce.getTrusted($sce.HTML, result.previewText))
          .toEqual('<p>Original HTML content string</p>');
    });
    

    【讨论】:

    【解决方案4】:

    另一种选择是使用 getTrustedHtml() 函数从 $$unwrapTrustedValue 获取 html 字符串值。

    vm.user.bio = $sce.getTrustedHtml(vm.user.bio);
    

    【讨论】:

      猜你喜欢
      • 2021-10-07
      • 2019-11-02
      • 2016-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多